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)
@@ -128,7 +169,7 @@ def sort_tree(modeladmin, request, queryset):
class KeywordAdmin(TreeAdmin):
form = movenodeform_factory(Keyword)
ordering = ('space', 'path',)
search_fields = ('name', )
search_fields = ('name',)
actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]
@@ -171,13 +212,15 @@ class RecipeAdmin(admin.ModelAdmin):
admin.site.register(Recipe, RecipeAdmin)
admin.site.register(Unit)
# admin.site.register(FoodInheritField)
class FoodAdmin(TreeAdmin):
form = movenodeform_factory(Keyword)
ordering = ('space', 'path',)
search_fields = ('name', )
search_fields = ('name',)
actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]

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" />
<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"/>
</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" />
<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>
@@ -86,17 +116,22 @@
:href="'#section-' + sectionID(x, i)"
:aria-expanded="'true' ? x == 'false' : 'true'"
>
<i class="fa fa-chevron-right rotate" />
{{ i }}
<i class="fa fa-chevron-right rotate"/>
<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>
@@ -166,8 +204,10 @@
:header="$t('SupermarketName')"
>
<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-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>
</div>
</b-card>
@@ -176,29 +216,21 @@
<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)
"
>
<i class="btn fas fa-edit fa-lg px-0" :class="s.editmode ? 'text-success' : 'text-muted'" />
@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)">
<i class="btn fas fa-trash fa-lg px-2 text-muted" />
<i class="btn fas fa-trash fa-lg px-2 text-muted"/>
</b-button>
</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>
@@ -237,13 +270,19 @@
:header="$t('CategoryName')"
>
<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-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>
</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"
@@ -264,13 +303,14 @@
>
{{ 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" />
<i class="btn fas fa-trash fa-lg px-2 text-muted"/>
</b-button>
</b-card>
</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" />
<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"/>
<draggable
class="list-group"
:list="notSupermarketCategory"
@@ -282,10 +322,11 @@
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" />
<i class="btn fas fa-trash fa-lg px-2 text-muted"/>
</b-button>
</b-card>
</transition-group>
@@ -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,93 +511,87 @@
<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="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>
</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>
<script>
import Vue from "vue"
import { BootstrapVue } from "bootstrap-vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import VueCookies from "vue-cookies"
@@ -566,8 +606,8 @@ import GenericPill from "@/components/GenericPill"
import LookupInput from "@/components/Modals/LookupInput"
import draggable from "vuedraggable"
import { ApiMixin, getUserPreference, StandardToasts, makeToast } from "@/utils/utils"
import { ApiApiFactory } from "@/utils/openapi/api"
import {ApiMixin, getUserPreference, StandardToasts, makeToast} from "@/utils/utils"
import {ApiApiFactory} from "@/utils/openapi/api"
Vue.use(BootstrapVue)
Vue.use(VueCookies)
@@ -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: [],
@@ -605,8 +657,8 @@ export default {
csv_prefix: undefined,
shopping_add_onhand: true,
},
new_supermarket: { entrymode: false, value: undefined, editmode: undefined },
new_category: { entrymode: false, value: undefined },
new_supermarket: {entrymode: false, value: undefined, editmode: undefined},
new_category: {entrymode: false, value: undefined},
autosync_id: undefined,
auto_sync_running: false,
show_delay: false,
@@ -615,7 +667,7 @@ export default {
fields: ["checked", "amount", "category", "unit", "food", "recipe", "details"],
loading: true,
entrymode: false,
new_item: { amount: 1, unit: undefined, food: undefined, ingredient: undefined },
new_item: {amount: 1, unit: undefined, food: undefined, ingredient: undefined},
online: true,
}
},
@@ -652,7 +704,7 @@ export default {
shopping_list = shopping_list.filter((x) => x?.food?.supermarket_category)
}
var groups = { false: {}, true: {} } // force unchecked to always be first
var groups = {false: {}, true: {}} // force unchecked to always be first
if (this.selected_supermarket) {
let super_cats = this.supermarkets
.filter((x) => x.id === this.selected_supermarket)
@@ -692,7 +744,7 @@ export default {
},
csvData() {
return this.items.map((x) => {
return { amount: x.amount, unit: x.unit?.name ?? "", food: x.food?.name ?? "" }
return {amount: x.amount, unit: x.unit?.name ?? "", food: x.food?.name ?? ""}
})
},
defaultDelay() {
@@ -790,11 +842,11 @@ export default {
// this.genericAPI inherited from ApiMixin
addItem: function () {
if (this.entry_mode_simple) {
this.genericPostAPI("api_ingredient_from_string", { text: this.new_item.ingredient }).then((result) => {
this.genericPostAPI("api_ingredient_from_string", {text: this.new_item.ingredient}).then((result) => {
this.new_item = {
amount: result.data.amount,
unit: { name: result.data.unit },
food: { name: result.data.food },
unit: {name: result.data.unit},
food: {name: result.data.food},
}
this.addEntry()
})
@@ -812,7 +864,7 @@ export default {
} else {
console.log("no data returned")
}
this.new_item = { amount: 1, unit: undefined, food: undefined, ingredient: undefined }
this.new_item = {amount: 1, unit: undefined, food: undefined, ingredient: undefined}
this.clear += 1
})
.catch((err) => {
@@ -862,7 +914,7 @@ export default {
if (Array.isArray(item)) {
item = item.map((x) => {
return { ...x, delay_until: delay_date }
return {...x, delay_until: delay_date}
})
entries = item.map((x) => x.id)
} else {
@@ -871,7 +923,7 @@ export default {
}
entries.forEach((entry) => {
promises.push(this.saveThis({ id: entry, delay_until: delay_date }, false))
promises.push(this.saveThis({id: entry, delay_until: delay_date}, false))
})
Promise.all(promises).then(() => {
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE)
@@ -917,7 +969,7 @@ export default {
},
editSupermarket(s) {
if (!s.editmode) {
this.new_supermarket = { entrymode: false, value: undefined, editmode: undefined }
this.new_supermarket = {entrymode: false, value: undefined, editmode: undefined}
this.supermarkets.map((x) => (x.editmode = false))
} else {
this.new_supermarket.value = s
@@ -938,7 +990,7 @@ export default {
let params = {}
params.supermarket = this.selected_supermarket
params.options = { query: { recent: 1 } }
params.options = {query: {recent: 1}}
if (autosync) {
params.options.query["autosync"] = 1
} else {
@@ -971,7 +1023,7 @@ export default {
})
},
getThis: function (id) {
return this.genericAPI(this.Models.SHOPPING_CATEGORY, this.Actions.FETCH, { id: id })
return this.genericAPI(this.Models.SHOPPING_CATEGORY, this.Actions.FETCH, {id: id})
},
mergeShoppingList: function (data) {
this.items.map((x) =>
@@ -996,7 +1048,7 @@ export default {
this.updateFood(food, "supermarket_category").then((result) => {
this.items = this.items.map((x) => {
if (x.food.id === food.id) {
return { ...x, food: { ...x.food, supermarket_category: supermarket_category } }
return {...x, food: {...x.food, supermarket_category: supermarket_category}}
} else {
return x
}
@@ -1019,7 +1071,8 @@ export default {
})
.then((entries) => {
entries.forEach((x) => {
api.destroyShoppingListEntry(x).then((result) => {})
api.destroyShoppingListEntry(x).then((result) => {
})
})
})
},
@@ -1084,7 +1137,7 @@ export default {
if (update.checked) {
completed_at = new Date().toISOString()
}
promises.push(this.saveThis({ id: id, checked: update.checked }, false))
promises.push(this.saveThis({id: id, checked: update.checked}, false))
let item = this.items.filter((entry) => entry.id == id)[0]
Vue.set(item, "checked", update.checked)
@@ -1119,13 +1172,13 @@ export default {
updateServings(e, plan) {
// maybe this needs debounced?
let api = new ApiApiFactory()
api.partialUpdateShoppingListRecipe(plan, { id: plan, servings: e }).then(() => {
api.partialUpdateShoppingListRecipe(plan, {id: plan, servings: e}).then(() => {
this.getShoppingList()
})
},
addCategory: function () {
let api = new ApiApiFactory()
api.createSupermarketCategory({ name: this.new_category.value })
api.createSupermarketCategory({name: this.new_category.value})
.then((result) => {
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
this.shopping_categories.push(result.data)
@@ -1138,7 +1191,7 @@ export default {
},
addSupermarket: function () {
let api = new ApiApiFactory()
api.createSupermarket({ name: this.new_supermarket.value })
api.createSupermarket({name: this.new_supermarket.value})
.then((result) => {
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
this.supermarkets.push(result.data)
@@ -1158,7 +1211,7 @@ export default {
var promises = []
supermarket.category_to_supermarket.forEach((x, i) => {
x.order = i
promises.push(apiClient.partialUpdateSupermarketCategoryRelation(x.id, { order: i }))
promises.push(apiClient.partialUpdateSupermarketCategoryRelation(x.id, {order: i}))
})
return Promise.all(promises).then(() => {
return supermarket
@@ -1226,7 +1279,7 @@ export default {
return item?.category?.name ?? item.name
},
updateOnlineStatus(e) {
const { type } = e
const {type} = e
this.online = type === "online"
},
beforeDestroy() {
@@ -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>
@@ -126,11 +158,11 @@
<script>
import Vue from "vue"
import { BootstrapVue } from "bootstrap-vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import ContextMenu from "@/components/ContextMenu/ContextMenu"
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
import { ApiMixin } from "@/utils/utils"
import {ApiMixin} from "@/utils/utils"
import RecipeCard from "./RecipeCard.vue"
Vue.use(BootstrapVue)
@@ -140,12 +172,12 @@ export default {
// or i'm capturing it incorrectly
name: "ShoppingLineItem",
mixins: [ApiMixin],
components: { RecipeCard, ContextMenu, ContextMenuItem },
components: {RecipeCard, ContextMenu, ContextMenuItem},
props: {
entries: {
type: Array,
},
groupby: { type: String },
groupby: {type: String},
},
data() {
return {
@@ -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
@@ -265,7 +300,7 @@ export default {
return [this.$t("Added_by"), item?.created_by.username, "@", this.formatDate(item.created_at)].join(" ")
},
openRecipeCard: function (e, item) {
this.genericAPI(this.Models.RECIPE, this.Actions.FETCH, { id: item.recipe_mealplan.recipe }).then((result) => {
this.genericAPI(this.Models.RECIPE, this.Actions.FETCH, {id: item.recipe_mealplan.recipe}).then((result) => {
let recipe = result.data
recipe.steps = undefined
this.recipe = true
@@ -275,9 +310,9 @@ export default {
updateChecked: function (e, item) {
let update = undefined
if (!item) {
update = { entries: this.entries.map((x) => x.id), checked: !this.formatChecked }
update = {entries: this.entries.map((x) => x.id), checked: !this.formatChecked}
} else {
update = { entries: [item], checked: !item.checked }
update = {entries: [item], checked: !item.checked}
}
this.$emit("update-checkbox", update)
},
@@ -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>