mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-08 23:58:15 -05:00
Merge branch 'develop' into facet-fix
This commit is contained in:
@@ -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]
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>  
|
||||
{{ 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] }}   {{ Object.entries(formatAmount)[0][0] }}</div>
|
||||
<div class="small" v-else v-for="(x, i) in Object.entries(formatAmount)" :key="i">{{ x[1] }}   {{ 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] }}  
|
||||
{{ 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) }}  
|
||||
{{ 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>
|
||||
|
||||
Reference in New Issue
Block a user