Merge branch 'develop' into facet-fix

This commit is contained in:
vabene1111
2022-01-18 07:59:32 +01:00
committed by GitHub
7 changed files with 1641 additions and 1460 deletions

View File

@@ -15,7 +15,7 @@ from .models import (BookmarkletImport, Comment, CookLog, Food, FoodInheritField
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
TelegramBot, Unit, UserFile, UserPreference, ViewLog)
TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation)
class CustomUserAdmin(UserAdmin):
@@ -29,11 +29,52 @@ admin.site.register(User, CustomUserAdmin)
admin.site.unregister(Group)
@admin.action(description='Delete all data from a space')
def delete_space_action(modeladmin, request, queryset):
for space in queryset:
CookLog.objects.filter(space=space).delete()
ViewLog.objects.filter(space=space).delete()
ImportLog.objects.filter(space=space).delete()
BookmarkletImport.objects.filter(space=space).delete()
Comment.objects.filter(recipe__space=space).delete()
Keyword.objects.filter(space=space).delete()
Ingredient.objects.filter(space=space).delete()
Food.objects.filter(space=space).delete()
Unit.objects.filter(space=space).delete()
Step.objects.filter(space=space).delete()
NutritionInformation.objects.filter(space=space).delete()
RecipeBookEntry.objects.filter(book__space=space).delete()
RecipeBook.objects.filter(space=space).delete()
MealType.objects.filter(space=space).delete()
MealPlan.objects.filter(space=space).delete()
ShareLink.objects.filter(space=space).delete()
Recipe.objects.filter(space=space).delete()
RecipeImport.objects.filter(space=space).delete()
SyncLog.objects.filter(sync__space=space).delete()
Sync.objects.filter(space=space).delete()
Storage.objects.filter(space=space).delete()
ShoppingListEntry.objects.filter(shoppinglist__space=space).delete()
ShoppingListRecipe.objects.filter(shoppinglist__space=space).delete()
ShoppingList.objects.filter(space=space).delete()
SupermarketCategoryRelation.objects.filter(supermarket__space=space).delete()
SupermarketCategory.objects.filter(space=space).delete()
Supermarket.objects.filter(space=space).delete()
InviteLink.objects.filter(space=space).delete()
UserFile.objects.filter(space=space).delete()
Automation.objects.filter(space=space).delete()
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
search_fields = ('name', 'created_by__username')
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
date_hierarchy = 'created_at'
actions = [delete_space_action]
admin.site.register(Space, SpaceAdmin)
@@ -171,6 +212,8 @@ class RecipeAdmin(admin.ModelAdmin):
admin.site.register(Recipe, RecipeAdmin)
admin.site.register(Unit)
# admin.site.register(FoodInheritField)

View File

@@ -165,9 +165,10 @@ class FoodInheritFieldSerializer(WritableNestedModelSerializer):
read_only_fields = ['id']
class UserPreferenceSerializer(serializers.ModelSerializer):
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)
def create(self, validated_data):
if not validated_data.get('user', None):

View File

@@ -45,21 +45,15 @@ def hook(request, token):
tb.save()
if tb.chat_id == str(data['message']['chat']['id']):
sl = ShoppingList.objects.filter(Q(created_by=tb.created_by)).filter(finished=False, space=tb.space).order_by('-created_at').first()
if not sl:
sl = ShoppingList.objects.create(created_by=tb.created_by, space=tb.space)
request.space = tb.space # TODO this is likely a bad idea. Verify and test
request.user = tb.created_by
ingredient_parser = IngredientParser(request, False)
amount, unit, ingredient, note = ingredient_parser.parse(data['message']['text'])
f = ingredient_parser.get_food(ingredient)
u = ingredient_parser.get_unit(unit)
sl.entries.add(
ShoppingListEntry.objects.create(
food=f, unit=u, amount=amount
)
)
ShoppingListEntry.objects.create(food=f, unit=u, amount=amount, created_by=request.user, space=request.space)
return JsonResponse({'data': data['message']['text']})
except Exception:
pass

View File

@@ -1,81 +1,111 @@
<template>
<div id="app" style="margin-bottom: 4vh">
<b-alert :show="!online" dismissible class="small float-up" variant="warning">{{ $t("OfflineAlert") }}</b-alert>
<div class="row float-top">
<div class="row float-top pl-0 pr-0">
<div class="col-auto no-gutter ml-auto">
<b-button variant="link" class="px-0">
<i class="btn fas fa-plus-circle fa-lg px-0" @click="entrymode = !entrymode" :class="entrymode ? 'text-success' : 'text-muted'" />
<b-button variant="link" class="px-1 pt-0 pb-1 d-none d-md-inline-block">
<i class="btn fas fa-plus-circle fa-lg px-0" @click="entrymode = !entrymode"
:class="entrymode ? 'text-success' : 'text-muted'"/>
</b-button>
<b-button variant="link" class="px-1">
<i class="fas fa-download fa-lg nav-link dropdown-toggle text-muted px-1" id="downloadShoppingLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
<b-button variant="link" class="px-1 pt-0 pb-1 d-none d-md-inline-block">
<i class="fas fa-download fa-lg nav-link dropdown-toggle text-muted px-1" id="downloadShoppingLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
<div class="dropdown-menu dropdown-menu-center" aria-labelledby="downloadShoppingLink">
<DownloadPDF dom="#shoppinglist" name="shopping.pdf" :label="$t('download_pdf')" icon="far fa-file-pdf"/>
<DownloadCSV :items="csvData" :delim="settings.csv_delim" name="shopping.csv" :label="$t('download_csv')" icon="fas fa-file-csv" />
<CopyToClipboard :items="csvData" :settings="settings" :label="$t('copy_to_clipboard')" icon="fas fa-clipboard-list" />
<CopyToClipboard :items="csvData" :settings="settings" format="table" :label="$t('copy_markdown_table')" icon="fab fa-markdown" />
<DownloadCSV :items="csvData" :delim="settings.csv_delim" name="shopping.csv" :label="$t('download_csv')"
icon="fas fa-file-csv"/>
<CopyToClipboard :items="csvData" :settings="settings" :label="$t('copy_to_clipboard')"
icon="fas fa-clipboard-list"/>
<CopyToClipboard :items="csvData" :settings="settings" format="table" :label="$t('copy_markdown_table')"
icon="fab fa-markdown"/>
</div>
</b-button>
<b-button variant="link" id="id_filters_button" class="px-1">
<i class="btn fas fa-filter text-decoration-none fa-lg px-1" :class="filterApplied ? 'text-danger' : 'text-muted'" />
<b-button variant="link" id="id_filters_button" class="px-1 pt-0 pb-1">
<i class="btn fas fa-filter text-decoration-none fa-lg px-1"
:class="filterApplied ? 'text-danger' : 'text-muted'"/>
</b-button>
</div>
</div>
<b-tabs content-class="mt-3">
<b-tabs content-class="mt-3" v-model="current_tab">
<!-- shopping list tab -->
<b-tab active>
<template #title> <b-spinner v-if="loading" type="border" small></b-spinner> {{ $t("Shopping_list") }} </template>
<div class="container" id="shoppinglist">
<template #title>
<b-spinner v-if="loading" type="border" small></b-spinner>
{{ $t("Shopping_list") }}
</template>
<div class="container p-0 pr-lg-5 pl-lg-5" id="shoppinglist">
<div class="row">
<div class="col col-md-12">
<div class="col col-md-12 p-0 p-lg-3">
<div role="tablist">
<!-- add to shopping form -->
<b-row class="row justify-content-md-center" v-if="entrymode">
<b-col cols="12" sm="4" md="2" v-if="!entry_mode_simple">
<b-form-input min="1" type="number" :description="$t('Amount')" v-model="new_item.amount"></b-form-input>
<b-row class="justify-content-md-center align-items-center pl-1 pr-1" v-if="entrymode">
<b-col cols="12" md="3" v-if="!entry_mode_simple" class="d-none d-md-block mt-1">
<b-form-input size="lg" min="1" type="number" :description="$t('Amount')"
v-model="new_item.amount"
style="font-size: 16px;border-radius: 5px !important;border: 1px solid #e8e8e8 !important;"></b-form-input>
</b-col>
<b-col cols="12" sm="8" md="3" v-if="!entry_mode_simple">
<lookup-input :form="formUnit" :model="Models.UNIT" @change="new_item.unit = $event" :show_label="false" :clear="clear" />
<b-col cols="12" md="4" v-if="!entry_mode_simple" class="mt-1">
<lookup-input :class_list="'mb-0'" :form="formUnit" :model="Models.UNIT"
@change="new_item.unit = $event"
:show_label="false" :clear="clear"/>
</b-col>
<b-col cols="12" sm="8" md="4" v-if="!entry_mode_simple">
<lookup-input :form="formFood" :model="Models.FOOD" @change="new_item.food = $event" :show_label="false" :clear="clear" />
<b-col cols="12" md="4" v-if="!entry_mode_simple" class="mt-1">
<lookup-input :class_list="'mb-0'" :form="formFood" :model="Models.FOOD"
@change="new_item.food = $event"
:show_label="false" :clear="clear"/>
</b-col>
<b-col cols="12" sm="8" v-if="entry_mode_simple">
<b-form-input type="text" :placeholder="$t('QuickEntry')" v-model="new_item.ingredient" @keyup.enter="addItem"></b-form-input>
<b-col cols="12" md="11" v-if="entry_mode_simple" class="mt-1">
<b-form-input size="lg" type="text" :placeholder="$t('QuickEntry')" v-model="new_item.ingredient"
@keyup.enter="addItem"></b-form-input>
</b-col>
<b-col cols="12" sm="4" md="1">
<b-col cols="12" md="1" class="d-none d-md-block mt-1">
<b-button variant="link" class="px-0">
<i class="btn fas fa-cart-plus fa-lg px-0 text-success" @click="addItem"/>
</b-button>
</b-col>
<b-col cols="12" md="3" v-if="!entry_mode_simple" class="d-block d-md-none mt-1">
<b-row>
<b-col cols="9">
<b-form-input size="lg" min="1" type="number" :description="$t('Amount')"
v-model="new_item.amount"
style="font-size: 16px;border-radius: 5px !important;border: 1px solid #e8e8e8 !important;"></b-form-input>
</b-col>
<b-col cols="3" class="flex-grow-1">
<b-button variant="success" class="p-0 pt-1 w-100 h-100">
<i class="btn fas fa-cart-plus fa-lg" @click="addItem"/>
</b-button>
</b-col>
</b-row>
</b-col>
</b-row>
<b-row class="row justify-content-md-end" v-if="entrymode">
<b-row class="row justify-content-around mt-2" v-if="entrymode">
<b-form-checkbox switch v-model="entry_mode_simple">
{{ $t("QuickEntry") }}
</b-form-checkbox>
<b-button variant="success" size="sm" class="d-flex d-md-none p-0" v-if="entry_mode_simple">
<i class="btn fas fa-cart-plus" @click="addItem"/>
</b-button>
</b-row>
<!-- shopping list table -->
<div v-if="items && items.length > 0">
<div v-for="(done, x) in Sections" :key="x">
<div v-if="x == 'true'">
<hr />
<hr />
<h4>{{ $t("Completed") }}</h4>
<h4 class="pl-2 pl-md-0">{{ $t("Completed") }}</h4>
</div>
<div v-for="(s, i) in done" :key="i">
<h5 v-if="Object.entries(s).length > 0">
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true">
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true"
v-if="Object.entries(s).length > 0">
<button
aria-haspopup="true"
aria-expanded="false"
type="button"
class="btn dropdown-toggle btn-link text-decoration-none text-dark pr-2 dropdown-toggle-no-caret"
@click.stop="openContextMenu($event, s, true)"
>
@click.stop="openContextMenu($event, s, true)">
<i class="fas fa-ellipsis-v fa-lg"></i>
</button>
@@ -87,16 +117,21 @@
:aria-expanded="'true' ? x == 'false' : 'true'"
>
<i class="fa fa-chevron-right rotate"/>
{{ i }}
<span class="h5 ml-2 text-secondary">{{ i }}</span>
</b-button>
</div>
</h5>
<div class="collapse" :id="'section-' + sectionID(x, i)" visible role="tabpanel" :class="{ show: x == 'false' }">
<div class="collapse" :id="'section-' + sectionID(x, i)" visible role="tabpanel"
:class="{ show: x == 'false' }">
<!-- passing an array of values to the table grouped by Food -->
<transition-group name="slider-fade" mode="out-in">
<div v-for="(entries, x) in Object.entries(s)" :key="x">
<ShoppingLineItem :entries="entries[1]" :groupby="group_by" @open-context-menu="openContextMenu" @update-checkbox="updateChecked" />
<ShoppingLineItem :entries="entries[1]" :groupby="group_by"
@open-context-menu="openContextMenu" @update-checkbox="updateChecked"/>
</div>
</transition-group>
</div>
</div>
</div>
@@ -108,7 +143,7 @@
</b-tab>
<!-- recipe tab -->
<b-tab :title="$t('Recipes')">
<table class="table w-75">
<table class="table w-100">
<thead>
<tr>
<th scope="col">{{ $t("Meal_Plan") }}</th>
@@ -121,10 +156,12 @@
<td>{{ r.recipe_mealplan.name }}</td>
<td>{{ r.recipe_mealplan.recipe_name }}</td>
<td class="block-inline">
<b-form-input min="1" type="number" :debounce="300" :value="r.recipe_mealplan.servings" @input="updateServings($event, r.list_recipe)"></b-form-input>
<b-form-input min="1" type="number" :debounce="300" :value="r.recipe_mealplan.servings"
@input="updateServings($event, r.list_recipe)"></b-form-input>
</td>
<td>
<i class="btn text-danger fas fa-trash fa-lg px-2 border-0" variant="link" :title="$t('Delete')" @click="deleteRecipe($event, r.list_recipe)" />
<i class="btn text-danger fas fa-trash fa-lg px-2 border-0" variant="link" :title="$t('Delete')"
@click="deleteRecipe($event, r.list_recipe)"/>
</td>
</tr>
</table>
@@ -151,7 +188,8 @@
})
"
>
<i class="btn fas fa-plus-circle fa-lg px-0" :class="new_supermarket.entrymode ? 'text-success' : 'text-muted'" />
<i class="btn fas fa-plus-circle fa-lg px-0"
:class="new_supermarket.entrymode ? 'text-success' : 'text-muted'"/>
</b-button>
</h4>
</template>
@@ -167,7 +205,9 @@
>
<div class="input-group">
<b-form-input type="text" :placeholder="$t('SupermarketName')" v-model="new_supermarket.value"/>
<b-button class="input-group-append" variant="success" @click="addSupermarket"><i class="pr-2 pt-1 fas fa-save"></i> {{ $t("Save") }}</b-button>
<b-button class="input-group-append" variant="success" @click="addSupermarket"><i
class="pr-2 pt-1 fas fa-save"></i> {{ $t("Save") }}
</b-button>
</div>
</b-card>
@@ -176,18 +216,10 @@
<b-card-title>
<div class="row">
<div class="col">{{ s.name }}</div>
<div class="col-auto text-right ml-auto">
<b-button
variant="link"
<b-button variant="link"
class="p-0 m-0"
@click="
s.editmode = !s.editmode
new_category.entrymode = false
new_supermarket.entrymode = false
editSupermarket(s)
"
>
@click="s.editmode = !s.editmode;new_category.entrymode = false;new_supermarket.entrymode = false;editSupermarket(s)">
<i class="btn fas fa-edit fa-lg px-0" :class="s.editmode ? 'text-success' : 'text-muted'"/>
</b-button>
<b-button variant="link" class="p-0 m-0" @click="deleteSupermarket(s)">
@@ -196,9 +228,9 @@
</div>
</div>
</b-card-title>
<b-card-body class="py-0">
<generic-pill :item_list="s.category_to_supermarket" label="category::name" color="info"></generic-pill>
<generic-pill :item_list="s.category_to_supermarket" label="category::name"
color="info"></generic-pill>
</b-card-body>
</b-card>
</b-card-body>
@@ -222,7 +254,8 @@
new_supermarket.entrymode = false
"
>
<i class="btn fas fa-plus-circle fa-lg px-0" :class="new_category.entrymode ? 'text-success' : 'text-muted'" />
<i class="btn fas fa-plus-circle fa-lg px-0"
:class="new_category.entrymode ? 'text-success' : 'text-muted'"/>
</b-button>
</div>
</div>
@@ -238,12 +271,18 @@
>
<div class="input-group">
<b-form-input type="text" :placeholder="$t('CategoryName')" v-model="new_category.value"/>
<b-button class="input-group-append" variant="success" @click="addCategory"><i class="pr-2 pt-1 fas fa-save"></i> {{ $t("Save") }}</b-button>
<b-button class="input-group-append" variant="success" @click="addCategory"><i
class="pr-2 pt-1 fas fa-save"></i> {{ $t("Save") }}
</b-button>
</div>
</b-card>
<b-card-sub-title v-if="new_supermarket.editmode" class="pt-0 pb-3">{{ $t("CategoryInstruction") }}</b-card-sub-title>
<b-card v-if="new_supermarket.editmode && supermarketCategory.length === 0" class="m-0 p-0 font-weight-bold no-body" border-variant="success" v-bind:key="-1" />
<b-card-sub-title v-if="new_supermarket.editmode" class="pt-0 pb-3">{{
$t("CategoryInstruction")
}}
</b-card-sub-title>
<b-card v-if="new_supermarket.editmode && supermarketCategory.length === 0"
class="m-0 p-0 font-weight-bold no-body" border-variant="success" v-bind:key="-1"/>
<draggable
class="list-group"
:list="supermarketCategory"
@@ -270,7 +309,8 @@
</transition-group>
</draggable>
<hr style="height: 2px; background-color: black" v-if="new_supermarket.editmode"/>
<b-card v-if="new_supermarket.editmode && notSupermarketCategory.length === 0" v-bind:key="-2" class="m-0 p-0 font-weight-bold no-body" border-variant="danger" />
<b-card v-if="new_supermarket.editmode && notSupermarketCategory.length === 0" v-bind:key="-2"
class="m-0 p-0 font-weight-bold no-body" border-variant="danger"/>
<draggable
class="list-group"
:list="notSupermarketCategory"
@@ -282,7 +322,8 @@
v-bind="{ animation: 200 }"
>
<transition-group type="transition" :name="!drag ? 'flip-list' : null">
<b-card class="m-0 p-0 font-weight-bold no-body list-group-item" style="cursor: move" v-for="c in notSupermarketCategory" v-bind:key="c.id" :border-variant="'danger'">
<b-card class="m-0 p-0 font-weight-bold no-body list-group-item" style="cursor: move"
v-for="c in notSupermarketCategory" v-bind:key="c.id" :border-variant="'danger'">
{{ categoryName(c) }}
<b-button variant="link" class="p-0 m-0 float-right" @click="deleteCategory(c)">
<i class="btn fas fa-trash fa-lg px-2 text-muted"/>
@@ -297,12 +338,13 @@
<!-- settings tab -->
<b-tab :title="$t('Settings')">
<div class="row justify-content-center">
<div class="col col-md-4 col-sm-8">
<div class="col-12 col-md-8">
<b-card class="no-body">
<div class="row">
<div class="col col-md-6">{{ $t("mealplan_autoadd_shopping") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" size="sm" v-model="settings.mealplan_autoadd_shopping" @change="saveSettings" />
<input type="checkbox" class="form-control-sm" v-model="settings.mealplan_autoadd_shopping"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -314,7 +356,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("mealplan_autoadd_shopping") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" size="sm" v-model="settings.mealplan_autoexclude_onhand" @change="saveSettings" />
<input type="checkbox" class="form-control-sm" v-model="settings.mealplan_autoexclude_onhand"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -327,7 +370,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("mealplan_autoinclude_related") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" size="sm" v-model="settings.mealplan_autoinclude_related" @change="saveSettings" />
<input type="checkbox" class="form-control-sm" v-model="settings.mealplan_autoinclude_related"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -343,10 +387,7 @@
<div class="col col-md-6 text-right">
<generic-multiselect
size="sm"
@change="
settings.shopping_share = $event.val
saveSettings()
"
@change="settings.shopping_share = $event.valsaveSettings()"
:model="Models.USER"
:initial_selection="settings.shopping_share"
label="username"
@@ -365,7 +406,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("shopping_auto_sync") }}</div>
<div class="col col-md-6 text-right">
<input type="number" size="sm" v-model="settings.shopping_auto_sync" @change="saveSettings" />
<input type="number" class="form-control-sm" v-model="settings.shopping_auto_sync"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -378,7 +420,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("shopping_add_onhand") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" size="sm" v-model="settings.shopping_add_onhand" @change="saveSettings" />
<input type="checkbox" class="form-control-sm" v-model="settings.shopping_add_onhand"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -391,7 +434,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("shopping_recent_days") }}</div>
<div class="col col-md-6 text-right">
<input type="number" size="sm" v-model="settings.shopping_recent_days" @change="saveSettings" />
<input type="number" class="form-control-sm" v-model="settings.shopping_recent_days"
@change="saveSettings"/>
</div>
</div>
@@ -405,7 +449,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("filter_to_supermarket") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" size="sm" v-model="settings.filter_to_supermarket" @change="saveSettings" />
<input type="checkbox" class="form-control-sm" v-model="settings.filter_to_supermarket"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -418,7 +463,8 @@
<div class="row">
<div class="col col-md-6">{{ $t("default_delay") }}</div>
<div class="col col-md-6 text-right">
<input type="number" size="sm" min="1" v-model="settings.default_delay" @change="saveSettings" />
<input type="number" class="form-control-sm" min="1" v-model="settings.default_delay"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -431,7 +477,7 @@
<div class="row">
<div class="col col-md-6">{{ $t("csv_delim_label") }}</div>
<div class="col col-md-6 text-right">
<input type="string" size="sm" v-model="settings.csv_delim" @change="saveSettings" />
<input class="form-control-sm" v-model="settings.csv_delim" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -444,7 +490,7 @@
<div class="row">
<div class="col col-md-6">{{ $t("csv_prefix_label") }}</div>
<div class="col col-md-6 text-right">
<input type="string" size="sm" v-model="settings.csv_prefix" @change="saveSettings" />
<input class="form-control-sm" v-model="settings.csv_prefix" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
@@ -465,87 +511,81 @@
<b-form-select v-model="group_by" :options="group_by_choices" size="sm"></b-form-select>
</b-form-group>
<b-form-group v-bind:label="$t('Supermarket')" label-for="popover-input-2" label-cols="6" class="mb-1">
<b-form-select v-model="selected_supermarket" :options="supermarkets" text-field="name" value-field="id" size="sm"></b-form-select>
<b-form-select v-model="selected_supermarket" :options="supermarkets" text-field="name" value-field="id"
size="sm"></b-form-select>
</b-form-group>
<!-- TODO: shade filters red when they are actually filtering content -->
<b-form-group v-bind:label="$t('ShowDelayed')" label-for="popover-input-3" content-cols="1" class="mb-1">
<b-form-checkbox v-model="show_delay"></b-form-checkbox>
</b-form-group>
<b-form-group v-bind:label="$t('ShowUncategorizedFood')" label-for="popover-input-4" content-cols="1" class="mb-1" v-if="!selected_supermarket">
<b-form-group v-bind:label="$t('ShowUncategorizedFood')" label-for="popover-input-4" content-cols="1"
class="mb-1" v-if="!selected_supermarket">
<b-form-checkbox v-model="show_undefined_categories"></b-form-checkbox>
</b-form-group>
<b-form-group v-bind:label="$t('SupermarketCategoriesOnly')" label-for="popover-input-5" content-cols="1" class="mb-1" v-if="selected_supermarket">
<b-form-group v-bind:label="$t('SupermarketCategoriesOnly')" label-for="popover-input-5" content-cols="1"
class="mb-1" v-if="selected_supermarket">
<b-form-checkbox v-model="supermarket_categories_only"></b-form-checkbox>
</b-form-group>
</div>
<div class="row" style="margin-top: 1vh; min-width: 300px">
<div class="col-12" style="text-align: right">
<b-button size="sm" variant="primary" class="mx-1" @click="resetFilters">{{ $t("Reset") }}</b-button>
<b-button size="sm" variant="secondary" class="mr-3" @click="$root.$emit('bv::hide::popover')">{{ $t("Close") }} </b-button>
<b-button size="sm" variant="secondary" class="mr-3" @click="$root.$emit('bv::hide::popover')">{{
$t("Close")
}}
</b-button>
</div>
</div>
</b-popover>
<ContextMenu ref="menu">
<template #menu="{ contextData }">
<ContextMenuItem
@click="
moveEntry($event, contextData)
$refs.menu.close()
"
>
<b-form-group label-cols="6" content-cols="6" class="text-nowrap m-0 mr-2">
<template #label>
<ContextMenuItem>
<b-row class="d-flex align-items-center mr-0">
<b-col cols="6">
<a class="dropdown-item p-2" href="#"><i class="fas fa-cubes"></i> {{ $t("MoveCategory") }}</a>
</template>
<span @click.prevent.stop @mouseup.prevent.stop>
<!-- would like to hide the dropdown value and only display value in button - not sure how to do that -->
<b-form-select class="mt-2 border-0" :options="shopping_categories" text-field="name" value-field="id" v-model="shopcat"></b-form-select>
</span>
</b-form-group>
</b-col>
<b-col cols="6 pl-1">
<b-form-select class="form-control form-control-sm" :options="shopping_categories" text-field="name"
value-field="id" v-model="shopcat"
@change="moveEntry($event, contextData);$refs.menu.close()"></b-form-select>
</b-col>
</b-row>
</ContextMenuItem>
<ContextMenuItem
@click="
$refs.menu.close()
onHand(contextData)
"
>
<ContextMenuItem @click="$refs.menu.close();onHand(contextData)">
<a class="dropdown-item p-2" href="#"><i class="fas fa-clipboard-check"></i> {{ $t("OnHand") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
$refs.menu.close()
delayThis(contextData)
"
>
<b-form-group label-cols="9" content-cols="3" class="text-nowrap m-0 mr-2">
<template #label>
<a class="dropdown-item p-2" href="#"><i class="far fa-hourglass"></i> {{ $t("DelayFor", { hours: delay }) }}</a>
</template>
<div @click.prevent.stop>
<b-form-input class="mt-2" min="0" type="number" v-model="delay"></b-form-input>
</div>
</b-form-group>
<ContextMenuItem @click="$refs.menu.close();delayThis(contextData)">
<a class="dropdown-item p-2" href="#"><i class="fas fa-hourglass"></i> {{
$t("DelayFor", {hours: delay})
}}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
$refs.menu.close()
updateChecked({ entries: contextData, checked: true })
"
>
<ContextMenuItem @click="$refs.menu.close();updateChecked({ entries: contextData, checked: true })">
<a class="dropdown-item p-2" href="#"><i class="fas fa-check-square"></i> {{ $t("mark_complete") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
$refs.menu.close()
deleteThis(contextData)
"
>
<ContextMenuItem @click="$refs.menu.close();deleteThis(contextData)">
<a class="dropdown-item p-2 text-danger" href="#"><i class="fas fa-trash"></i> {{ $t("Delete") }}</a>
</ContextMenuItem>
</template>
</ContextMenu>
<transition name="slided-fade">
<div class="row fixed-bottom p-2 b-1 border-top text-center d-flex d-md-none"
style="background: rgba(255, 255, 255, 0.6)"
v-if="current_tab === 0">
<div class="col-md-3 col-6">
<a class="btn btn-block btn-success shadow-none" @click="entrymode = !entrymode"
><i class="fas fa-cart-plus"></i>
{{ $t("New Entry") }}
</a>
</div>
<div class="col-md-3 col-6">
<a class="btn btn-block btn-secondary shadow-none"
><i class="fas fa-download"></i>
{{ $t("Export") }}
</a>
</div>
</div>
</transition>
</div>
</template>
@@ -576,12 +616,24 @@ let SETTINGS_COOKIE_NAME = "shopping_settings"
export default {
name: "ShoppingListView",
mixins: [ApiMixin],
components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect, GenericPill, draggable, LookupInput, DownloadPDF, DownloadCSV, CopyToClipboard },
components: {
ContextMenu,
ContextMenuItem,
ShoppingLineItem,
GenericMultiselect,
GenericPill,
draggable,
LookupInput,
DownloadPDF,
DownloadCSV,
CopyToClipboard
},
data() {
return {
// this.Models and this.Actions inherited from ApiMixin
items: [],
current_tab: 0,
group_by: "category",
group_by_choices: ["created_by", "category", "recipe"],
supermarkets: [],
@@ -1019,7 +1071,8 @@ export default {
})
.then((entries) => {
entries.forEach((x) => {
api.destroyShoppingListEntry(x).then((result) => {})
api.destroyShoppingListEntry(x).then((result) => {
})
})
})
},
@@ -1245,15 +1298,18 @@ export default {
-webkit-transition: all 0.25s linear;
transition: all 0.25s linear;
}
.btn[aria-expanded="true"] > .rotate {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.float-top {
padding-bottom: -3em;
margin-bottom: -3em;
}
.float-up {
padding-top: -3em;
margin-top: -3em;
@@ -1263,4 +1319,29 @@ export default {
opacity: 0.5;
background: #c8ebfb;
}
.slider-fade-enter-active, .slider-fade-leave-active {
transition: all 0.3s ease;
}
.slider-fade-enter, .slider-fade-leave-to
/* .slider-fade-leave-active below version 2.1.8 */
{
transform: translateX(10px);
opacity: 0;
}
.slided-fade-enter-active {
transition: all 0.3s ease;
}
.slided-fade-leave-active {
transition: all 0.1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slided-fade-enter,
.slided-fade-leave-to {
transform: translateY(10px);
opacity: 0;
}
</style>

View File

@@ -8,12 +8,12 @@
:class="{ 'border border-primary': over, shake: isError }"
:style="{ 'cursor:grab': useDrag }"
:draggable="useDrag"
@[useDrag&&`dragover`].prevent
@[useDrag&&`dragenter`].prevent
@[useDrag&&`dragstart`]="handleDragStart($event)"
@[useDrag&&`dragenter`]="handleDragEnter($event)"
@[useDrag&&`dragleave`]="handleDragLeave($event)"
@[useDrag&&`drop`]="handleDragDrop($event)"
@[useDrag&&`dragover`||``].prevent
@[useDrag&&`dragenter`||``].prevent
@[useDrag&&`dragstart`||``]="handleDragStart($event)"
@[useDrag&&`dragenter`||``]="handleDragEnter($event)"
@[useDrag&&`dragleave`||``]="handleDragLeave($event)"
@[useDrag&&`drop`||``]="handleDragDrop($event)"
>
<b-row no-gutters>
<b-col no-gutters class="col-sm-3">
@@ -27,6 +27,7 @@
<div class="m-0 text-truncate small text-muted" v-if="getFullname">{{ getFullname }}</div>
<generic-pill v-for="x in itemTags" :key="x.field" :item_list="itemList(x)" :label="x.label" :color="x.color" />
<generic-ordered-pill
v-for="x in itemOrderedTags"
:key="x.field"
@@ -37,6 +38,7 @@
:item="item"
@finish-action="finishAction"
/>
<div class="mt-auto mb-1" align="right">
<span v-if="item[child_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800" v-on:click="$emit('item-action', { action: 'get-children', source: item })">
<div v-if="!item.show_children">{{ item[child_count] }} {{ itemName }}</div>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group class="mb-3">
<b-form-group :class="class_list">
<template #label v-if="show_label">
{{ form.label }}
</template>
@@ -44,6 +44,7 @@ export default {
return undefined
},
},
class_list: {type: String, default: "mb-3"},
show_label: { type: Boolean, default: true },
clear: { type: Number },
},
@@ -82,7 +83,7 @@ export default {
} else {
arrayValues = [{ id: -1, name: this_value }]
}
if (this.form?.ordered && this.first_run && arrayValues.length > 0) {
if (this.form?.ordered && this.first_run) {
return this.flattenItems(arrayValues)
} else {
return arrayValues

View File

@@ -1,55 +1,73 @@
<template>
<div id="shopping_line_item">
<div class="col-12">
<b-container fluid>
<b-container fluid class="pr-0 pl-1 pl-md-3">
<!-- summary rows -->
<b-row align-h="start">
<b-col cols="12" sm="2">
<div style="position: static" class="btn-group">
<b-col cols="1" class="align-items-center d-flex">
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true">
<button
aria-haspopup="true"
aria-expanded="false"
type="button"
class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 dropdown-toggle-no-caret"
@click.stop="$emit('open-context-menu', $event, entries)"
>
@click.stop="$emit('open-context-menu', $event, entries)">
<i class="fas fa-ellipsis-v fa-lg"></i>
</button>
</div>
<input type="checkbox" class="text-right mx-3 mt-2" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
</b-col>
<b-col cols="1" class="justify-content-center align-items-center d-none d-md-flex">
<input type="checkbox" class="form-control form-control-sm checkbox-control" :checked="formatChecked"
@change="updateChecked"
:key="entries[0].id"/>
</b-col>
<b-col cols="8" md="9">
<b-row class="d-flex h-100" @click.stop="$emit('open-context-menu', $event, entries)">
<b-col cols="6" md="6" class="d-flex align-items-center" v-if="Object.entries(formatAmount).length == 1">
<div><strong>{{ Object.entries(formatAmount)[0][1] }}</strong> &ensp;
{{ Object.entries(formatAmount)[0][0] }}
</div>
</b-col>
<b-col cols="12" sm="10">
<b-row>
<b-col cols="6" sm="3">
<div v-if="Object.entries(formatAmount).length == 1">{{ Object.entries(formatAmount)[0][1] }} &ensp; {{ Object.entries(formatAmount)[0][0] }}</div>
<div class="small" v-else v-for="(x, i) in Object.entries(formatAmount)" :key="i">{{ x[1] }} &ensp; {{ x[0] }}</div>
<b-col cols="6" md="6" class="d-flex flex-column" v-if="Object.entries(formatAmount).length != 1">
<div class="small" v-for="(x, i) in Object.entries(formatAmount)" :key="i">{{ x[1] }} &ensp;
{{ x[0] }}
</div>
</b-col>
<b-col cols="6" sm="7">
<b-col cols="6" md="3" class="align-items-center d-flex pl-0 pr-0 pl-md-2 pr-md-2">
{{ formatFood }}
</b-col>
<b-col cols="6" sm="2" data-html2canvas-ignore="true">
<b-button size="sm" @click="showDetails = !showDetails" class="mr-2" variant="link">
<div class="text-nowrap">{{ showDetails ? "Hide" : "Show" }} Details</div>
<b-col cols="3" data-html2canvas-ignore="true" class="align-items-center d-none d-md-flex">
<b-button size="sm" @click="showDetails = !showDetails" class="p-0 mr-0 mr-md-2 p-md-2" variant="link">
<div class="text-nowrap"><i class="fa fa-chevron-right rotate"
:class="showDetails ? 'rotated' : ''"></i> <span
class="d-none d-md-inline-block">{{ $t('Details') }}</span>
</div>
</b-button>
</b-col>
</b-row>
</b-col>
<b-col cols="3" md="2" class="justify-content-start align-items-center d-flex d-md-none">
<b-button size="sm" @click="showDetails = !showDetails" class="d-inline-block d-md-none p-0" variant="link">
<div class="text-nowrap"><i class="fa fa-chevron-right rotate" :class="showDetails ? 'rotated' : ''"></i>
</div>
</b-button>
<input type="checkbox" class="form-control form-control-sm checkbox-control-mobile" :checked="formatChecked"
@change="updateChecked"
:key="entries[0].id"/>
</b-col>
</b-row>
<b-row align-h="center">
<b-row align-h="center" class="d-none d-md-flex">
<b-col cols="12">
<div class="small text-muted text-truncate">{{ formatHint }}</div>
</b-col>
</b-row>
</b-container>
<!-- detail rows -->
<div class="card no-body" v-if="showDetails">
<div class="card no-body mb-1 pt-2 align-content-center ml-2" v-if="showDetails">
<b-container fluid>
<div v-for="e in entries" :key="e.id">
<b-row class="ml-2 small">
<b-col cols="6" md="4" class="overflow-hidden text-nowrap">
<div v-for="(e, x) in entries" :key="e.id">
<b-row class="small justify-content-around">
<b-col cols="auto" md="4" class="overflow-hidden text-nowrap">
<button
aria-haspopup="true"
aria-expanded="false"
@@ -57,58 +75,72 @@
class="btn btn-link btn-sm m-0 p-0"
style="text-overflow: ellipsis"
@click.stop="openRecipeCard($event, e)"
@mouseover="openRecipeCard($event, e)"
>
@mouseover="openRecipeCard($event, e)">
{{ formatOneRecipe(e) }}
</button>
</b-col>
<b-col cols="6" md="4" class="col-md-4 text-muted">{{ formatOneMealPlan(e) }}</b-col>
<b-col cols="12" md="4" class="col-md-4 text-muted text-right overflow-hidden text-nowrap">
<b-col cols="auto" md="4" class="text-muted">{{ formatOneMealPlan(e) }}</b-col>
<b-col cols="auto" md="4" class="text-muted text-right overflow-hidden text-nowrap">
{{ formatOneCreatedBy(e) }}
<div v-if="formatOneCompletedAt(e)">{{ formatOneCompletedAt(e) }}</div>
</b-col>
</b-row>
<b-row class="ml-2 light">
<b-col cols="12" sm="2">
<div style="position: static" class="btn-group">
<b-row align-h="start">
<b-col cols="1" class="align-items-center d-flex">
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true">
<button
aria-haspopup="true"
aria-expanded="false"
type="button"
class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 dropdown-toggle-no-caret"
@click.stop="$emit('open-context-menu', $event, e)"
>
@click.stop="$emit('open-context-menu', $event, e)">
<i class="fas fa-ellipsis-v fa-lg"></i>
</button>
</div>
<input type="checkbox" class="text-right mx-3 mt-2" :checked="e.checked" @change="updateChecked($event, e)" />
</b-col>
<b-col cols="1" class="justify-content-center align-items-center d-none d-md-flex">
<input type="checkbox" class="form-control form-control-sm checkbox-control" :checked="formatChecked"
@change="updateChecked"
:key="entries[0].id"/>
</b-col>
<b-col cols="8" md="9">
<b-row class="d-flex justify-content-around">
<b-col cols="6" md="6" class="d-flex align-items-center">
<div>{{ formatOneAmount(e) }} &ensp;
{{ formatOneUnit(e) }}
</div>
</b-col>
<b-col cols="12" sm="10">
<b-row>
<b-col cols="2" sm="2" md="1" class="text-nowrap">{{ formatOneAmount(e) }}</b-col>
<b-col cols="10" sm="4" md="2" class="text-nowrap">{{ formatOneUnit(e) }}</b-col>
<b-col cols="12" sm="6" md="4" class="text-nowrap">{{ formatOneFood(e) }}</b-col>
<b-col cols="12" sm="6" md="5">
<div class="small" v-for="(n, i) in formatOneNote(e)" :key="i">{{ n }}</div>
<b-col cols="6" md="3" class="align-items-center d-flex pl-0 pr-0 pl-md-2 pr-md-2">
{{ formatOneFood(e) }}
</b-col>
<b-col cols="12" class="d-flex d-md-none">
<div class="small text-muted text-truncate" v-for="(n, i) in formatOneNote(e)" :key="i">{{ n }}</div>
</b-col>
</b-row>
</b-col>
<b-col cols="3" md="2" class="justify-content-start align-items-center d-flex d-md-none">
<input type="checkbox" class="form-control form-control-sm checkbox-control-mobile"
:checked="formatChecked"
@change="updateChecked"
:key="entries[0].id"/>
</b-col>
</b-row>
<hr class="w-75" />
<hr class="w-75" v-if="x !== entries.length -1"/>
<div class="pb-4" v-if="x === entries.length -1"></div>
</div>
</b-container>
</div>
<hr class="m-1" />
</div>
<hr class="m-1" v-if="!showDetails"/>
<ContextMenu ref="recipe_card" triggers="click, hover" :title="$t('Filters')" style="max-width: 300">
<template #menu="{ contextData }" v-if="recipe">
<ContextMenuItem><RecipeCard :recipe="contextData" :detail="false"></RecipeCard></ContextMenuItem>
<ContextMenuItem>
<RecipeCard :recipe="contextData" :detail="false"></RecipeCard>
</ContextMenuItem>
<ContextMenuItem @click="$refs.menu.close()">
<b-form-group label-cols="9" content-cols="3" class="text-nowrap m-0 mr-2">
<template #label>
@@ -223,7 +255,10 @@ export default {
if (!datetime) {
return
}
return Intl.DateTimeFormat(window.navigator.language, { dateStyle: "short", timeStyle: "short" }).format(Date.parse(datetime))
return Intl.DateTimeFormat(window.navigator.language, {
dateStyle: "short",
timeStyle: "short"
}).format(Date.parse(datetime))
},
formatOneAmount: function (item) {
return item?.amount ?? 1
@@ -296,4 +331,28 @@ export default {
/* left: 0; top: 50%; width: 100%; /* …with the top across the middle */
/* border-bottom: 1px solid #000; /* …and with a border on the top */
/* } */
.checkbox-control {
font-size: 0.6rem
}
.checkbox-control-mobile {
font-size: 1rem
}
.rotate {
-moz-transition: all 0.25s linear;
-webkit-transition: all 0.25s linear;
transition: all 0.25s linear;
}
.rotated {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.unit-badge-lg {
font-size: 1rem !important;
font-weight: 500 !important;
}
</style>