857 lines
40 KiB
HTML
857 lines
40 KiB
HTML
{% extends "base.html" %}
|
|
{% load django_tables2 %}
|
|
{% load crispy_forms_tags %}
|
|
{% load static %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Shopping List" %}{% endblock %}
|
|
|
|
{% block extra_head %}
|
|
{% include 'include/vue_base.html' %}
|
|
|
|
<link rel="stylesheet" href="{% static 'css/vue-multiselect-bs4.min.css' %}">
|
|
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
|
|
|
|
<script src="{% static 'js/Sortable.min.js' %}"></script>
|
|
<script src="{% static 'js/vuedraggable.umd.min.js' %}"></script>
|
|
|
|
<link rel="stylesheet" href="{% static 'css/pretty-checkbox.min.css' %}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="row">
|
|
<div class="col col-md-9">
|
|
<h2>{% trans 'Shopping List' %}</h2>
|
|
</div>
|
|
<div class="col col-mdd-3 text-right">
|
|
<b-form-checkbox switch size="lg" v-model="edit_mode"
|
|
@change="$forceUpdate()">{% trans 'Edit' %}</b-form-checkbox>
|
|
</div>
|
|
</div>
|
|
|
|
<template v-if="shopping_list !== undefined">
|
|
|
|
<div class="text-center" v-if="loading">
|
|
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
|
</div>
|
|
<div v-else-if="edit_mode">
|
|
<div class="row">
|
|
<div class="col col-md-6">
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fa fa-search"></i> {% trans 'Search' %}
|
|
</div>
|
|
<div class="card-body">
|
|
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
|
|
placeholder="{% trans 'Search Recipe' %}">
|
|
<ul class="list-group" style="margin-top: 8px">
|
|
<li class="list-group-item" v-for="x in recipes">
|
|
<div class="row flex-row" style="padding-left: 0.5vw; padding-right: 0.5vw">
|
|
<div class="flex-column flex-fill my-auto"><a v-bind:href="getRecipeUrl(x.id)"
|
|
target="_blank"
|
|
rel="nofollow norefferer">[[x.name]]</a>
|
|
</div>
|
|
<div class="flex-column align-self-end">
|
|
<button class="btn btn-outline-primary shadow-none"
|
|
@click="addRecipeToList(x)"><i
|
|
class="fa fa-plus"></i></button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fa fa-shopping-cart"></i> {% trans 'Shopping Recipes' %}
|
|
</div>
|
|
<div class="card-body">
|
|
<template v-if="shopping_list.recipes.length < 1">
|
|
{% trans 'No recipes selected' %}
|
|
</template>
|
|
<template v-else>
|
|
<div class="row flex-row my-auto" v-for="x in shopping_list.recipes"
|
|
style="margin-top: 1vh!important;">
|
|
<div class="flex-column align-self-start " style="margin-right: 0.4vw">
|
|
<button class="btn btn-outline-danger" @click="removeRecipeFromList(x)"><i
|
|
class="fa fa-trash"></i></button>
|
|
</div>
|
|
<div class="flex-grow-1 flex-column my-auto"><a v-bind:href="getRecipeUrl(x.recipe)"
|
|
target="_blank"
|
|
rel="nofollow norefferer">[[x.recipe_name]]</a>
|
|
</div>
|
|
<div class="flex-column align-self-end ">
|
|
<div class="input-group input-group-sm my-auto">
|
|
<div class="input-group-prepend">
|
|
<button class="text-muted btn btn-outline-primary shadow-none"
|
|
@click="((x.servings - 1) > 0) ? x.servings -= 1 : 1">-
|
|
</button>
|
|
</div>
|
|
<input class="form-control" type="number" v-model="x.servings">
|
|
<div class="input-group-append">
|
|
<button class="text-muted btn btn-outline-primary shadow-none"
|
|
@click="x.servings += 1">
|
|
+
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table table-sm" style="margin-top: 1vh">
|
|
|
|
<template v-for="c in display_categories">
|
|
<thead>
|
|
<tr>
|
|
<th colspan="5">[[c.name]]</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody is="draggable" :list="c.entries" tag="tbody" group="people" @sort="sortEntries"
|
|
@change="dragChanged(c, $event)">
|
|
<tr v-for="(element, index) in c.entries" :key="element.id">
|
|
<td class="handle"><i class="fas fa-sort"></i></td>
|
|
<td>[[element.amount]]</td>
|
|
<td>[[element.unit.name]]</td>
|
|
<td>[[element.food.name]]</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-danger" v-if="element.list_recipe === null"
|
|
@click="shopping_list.entries = shopping_list.entries.filter(item => item.id !== element.id)">
|
|
<i class="fa fa-trash"></i></button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</template>
|
|
|
|
</table>
|
|
|
|
|
|
<div class="row">
|
|
<div class="col col-md-3">
|
|
<input class="form-control" type="number" placeholder="{% trans 'Amount' %}"
|
|
v-model="new_entry.amount" ref="new_entry_amount">
|
|
</div>
|
|
<div class="col col-md-4">
|
|
<multiselect
|
|
v-tabindex
|
|
ref="unit"
|
|
v-model="new_entry.unit"
|
|
:options="units"
|
|
:close-on-select="true"
|
|
:clear-on-select="true"
|
|
:allow-empty="true"
|
|
:preserve-search="true"
|
|
placeholder="{% trans 'Select Unit' %}"
|
|
tag-placeholder="{% trans 'Create' %}"
|
|
select-label="{% trans 'Select' %}"
|
|
:taggable="true"
|
|
@tag="addUnitType"
|
|
label="name"
|
|
track-by="name"
|
|
:multiple="false"
|
|
:loading="units_loading"
|
|
@search-change="searchUnits">
|
|
</multiselect>
|
|
</div>
|
|
<div class="col col-md-4">
|
|
<multiselect
|
|
v-tabindex
|
|
ref="food"
|
|
v-model="new_entry.food"
|
|
:options="foods"
|
|
:close-on-select="true"
|
|
:clear-on-select="true"
|
|
:allow-empty="true"
|
|
:preserve-search="true"
|
|
placeholder="{% trans 'Select Food' %}"
|
|
tag-placeholder="{% trans 'Create' %}"
|
|
select-label="{% trans 'Select' %}"
|
|
:taggable="true"
|
|
@tag="addFoodType"
|
|
label="name"
|
|
track-by="name"
|
|
:multiple="false"
|
|
:loading="foods_loading"
|
|
@search-change="searchFoods">
|
|
</multiselect>
|
|
</div>
|
|
|
|
<div class="col col-md-1 my-auto text-right">
|
|
<button class="btn btn-success btn-lg" @click="addEntry()"><i class="fa fa-plus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col" style="text-align: right; margin-top: 1vh">
|
|
|
|
<div class="form-group form-check form-group-lg">
|
|
<input class="form-check-input" style="zoom:1.3;" type="checkbox"
|
|
v-model="shopping_list.finished" id="id_finished">
|
|
<label class="form-check-label" style="zoom:1.3;"
|
|
for="id_finished"> {% trans 'Finished' %}</label>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col" style="margin-top: 1vh">
|
|
<multiselect
|
|
v-tabindex
|
|
v-model="shopping_list.supermarket"
|
|
:options="supermarkets"
|
|
:close-on-select="true"
|
|
:clear-on-select="true"
|
|
:allow-empty="true"
|
|
:preserve-search="true"
|
|
placeholder="{% trans 'Select Supermarket' %}"
|
|
select-label="{% trans 'Select' %}"
|
|
label="name"
|
|
track-by="id"
|
|
:multiple="false"
|
|
:loading="supermarkets_loading"
|
|
@search-change="searchSupermarket">
|
|
</multiselect>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col" style="margin-top: 1vh">
|
|
<multiselect
|
|
v-tabindex
|
|
|
|
v-model="shopping_list.shared"
|
|
:options="users"
|
|
:close-on-select="true"
|
|
:clear-on-select="true"
|
|
:allow-empty="true"
|
|
:preserve-search="true"
|
|
placeholder="{% trans 'Select User' %}"
|
|
select-label="{% trans 'Select' %}"
|
|
label="username"
|
|
track-by="id"
|
|
:multiple="true"
|
|
:loading="users_loading"
|
|
@search-change="searchUsers">
|
|
</multiselect>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div v-else>
|
|
|
|
{% if request.user.userpreference.shopping_auto_sync > 0 %}
|
|
<div class="row" v-if="!onLine">
|
|
<div class="col col-md-12">
|
|
<div class="alert alert-warning" role="alert">
|
|
{% trans 'You are offline, shopping list might not syncronize.' %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row" style="margin-top: 8px">
|
|
<div class="col col-md-12">
|
|
<table class="table">
|
|
<template v-for="c in display_categories">
|
|
<template v-if="c.entries.filter(item => item.checked === false).length > 0">
|
|
<tr>
|
|
<td colspan="4">[[c.name]]</td>
|
|
</tr>
|
|
<tr v-for="x in c.entries">
|
|
<template v-if="!x.checked">
|
|
<td><input type="checkbox" style="zoom:1.4;" v-model="x.checked"
|
|
@change="entryChecked(x)">
|
|
</td>
|
|
<td>[[x.amount]]</td>
|
|
<td>[[x.unit.name]]</td>
|
|
<td>[[x.food.name]] <span class="text-muted" v-if="x.recipes.length > 0">([[x.recipes.join(', ')]])</span></td>
|
|
</template>
|
|
</tr>
|
|
</template>
|
|
</template>
|
|
<tr>
|
|
<td colspan="4"></td>
|
|
</tr>
|
|
<template v-for="c in display_categories">
|
|
|
|
<tr v-for="x in c.entries" class="text-muted">
|
|
<template v-if="x.checked">
|
|
<td><input type="checkbox" style="zoom:1.4;" v-model="x.checked"
|
|
@change="entryChecked(x)">
|
|
</td>
|
|
<td>[[x.amount]]</td>
|
|
<td>[[x.unit.name]]</td>
|
|
<td>[[x.food.name]]</td>
|
|
</template>
|
|
</tr>
|
|
</template>
|
|
|
|
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<div class="row" style="margin-top: 2vh">
|
|
<div class="col" style="text-align: right">
|
|
<b-button class="btn btn-info" v-b-modal.id_modal_export><i
|
|
class="fas fa-file-export"></i> {% trans 'Export' %}</b-button>
|
|
<button class="btn btn-success" @click="updateShoppingList()" v-if="edit_mode"><i
|
|
class="fas fa-save"></i> {% trans 'Save' %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<b-modal id="id_modal_export" title="{% trans 'Copy/Export' %}">
|
|
<div class="row">
|
|
<div class="col col-12">
|
|
<label>
|
|
{% trans 'List Prefix' %}
|
|
<input class="form-control" v-model="export_text_prefix">
|
|
</label>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col col-12">
|
|
<b-form-textarea class="form-control" max-rows="8" v-model="export_text">
|
|
|
|
</b-form-textarea>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</b-modal>
|
|
|
|
</template>
|
|
|
|
|
|
{% endblock %}
|
|
{% block script %}
|
|
|
|
<script src="{% url 'javascript-catalog' %}"></script>
|
|
<script type="application/javascript">
|
|
let csrftoken = Cookies.get('csrftoken');
|
|
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
|
|
|
|
Vue.component('vue-multiselect', window.VueMultiselect.default)
|
|
|
|
let app = new Vue({
|
|
components: {
|
|
Multiselect: window.VueMultiselect.default
|
|
},
|
|
delimiters: ['[[', ']]'],
|
|
el: '#id_base_container',
|
|
data: {
|
|
shopping_list_id: {% if shopping_list_id %}{{ shopping_list_id }}{% else %}null{% endif %},
|
|
loading: true,
|
|
edit_mode: false,
|
|
export_text_prefix: '', //TODO add userpreference
|
|
recipe_query: '',
|
|
recipes: [],
|
|
shopping_list: undefined,
|
|
new_entry: {
|
|
unit: undefined,
|
|
amount: undefined,
|
|
food: undefined,
|
|
},
|
|
foods: [],
|
|
foods_loading: false,
|
|
units: [],
|
|
units_loading: false,
|
|
supermarkets: [],
|
|
supermarkets_loading: false,
|
|
users: [],
|
|
users_loading: false,
|
|
onLine: navigator.onLine,
|
|
},
|
|
directives: {
|
|
tabindex: {
|
|
inserted(el) {
|
|
el.setAttribute('tabindex', 0);
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
servings_cache() {
|
|
let cache = {}
|
|
this.shopping_list.recipes.forEach((r) => {
|
|
cache[r.id] = r.servings;
|
|
})
|
|
return cache
|
|
},
|
|
recipe_cache() {
|
|
let cache = {}
|
|
this.shopping_list.recipes.forEach((r) => {
|
|
cache[r.id] = r.recipe_name;
|
|
})
|
|
return cache
|
|
},
|
|
display_categories() {
|
|
let categories = {
|
|
no_category: {
|
|
name: gettext('Uncategorized'),
|
|
id: -1,
|
|
entries: [],
|
|
order: 99999999
|
|
}
|
|
}
|
|
|
|
this.shopping_list.entries.forEach((e) => {
|
|
if (e.food.supermarket_category !== null) {
|
|
categories[e.food.supermarket_category.id] = {
|
|
name: e.food.supermarket_category.name,
|
|
id: e.food.supermarket_category.id,
|
|
order: 0,
|
|
entries: []
|
|
};
|
|
}
|
|
})
|
|
|
|
if (this.shopping_list.supermarket !== null) {
|
|
this.shopping_list.supermarket.category_to_supermarket.forEach(el => {
|
|
categories[el.category.id] = {
|
|
name: el.category.name,
|
|
id: el.category.id,
|
|
order: el.order,
|
|
entries: []
|
|
};
|
|
})
|
|
}
|
|
|
|
this.shopping_list.entries.forEach(element => {
|
|
let item = {}
|
|
Object.assign(item, element);
|
|
item.recipes = []
|
|
|
|
let entry = this.findMergeEntry(categories, item)
|
|
if (entry !== undefined) {
|
|
entry.amount += item.amount * this.servings_cache[item.list_recipe]
|
|
|
|
if (item.list_recipe !== null && entry.recipes.indexOf(this.recipe_cache[item.list_recipe]) === -1) {
|
|
entry.recipes.push(this.recipe_cache[item.list_recipe])
|
|
}
|
|
|
|
entry.entries.push(item.id)
|
|
} else {
|
|
if (item.list_recipe !== null) {
|
|
item.amount = item.amount * this.servings_cache[item.list_recipe]
|
|
}
|
|
item.unit = ((element.unit !== undefined && element.unit !== null) ? element.unit : {'name': ''})
|
|
item.entries = [element.id]
|
|
if (element.list_recipe !== null) {
|
|
item.recipes.push(this.recipe_cache[element.list_recipe])
|
|
}
|
|
if (item.food.supermarket_category !== null) {
|
|
categories[item.food.supermarket_category.id].entries.push(item)
|
|
} else {
|
|
categories['no_category'].entries.push(item)
|
|
}
|
|
}
|
|
});
|
|
|
|
let ordered_categories = []
|
|
for (let [i, v] of Object.entries(categories)) {
|
|
ordered_categories.push(v)
|
|
}
|
|
|
|
ordered_categories.sort(function (a, b) {
|
|
if (a.order < b.order) {
|
|
return -1
|
|
} else if (a.order > b.order) {
|
|
return 1
|
|
} else {
|
|
return 0
|
|
}
|
|
})
|
|
|
|
return ordered_categories
|
|
},
|
|
export_text() {
|
|
let text = ''
|
|
for (let c of this.display_categories) {
|
|
for (let e of c.entries.filter(item => item.checked === false)) {
|
|
text += `${this.export_text_prefix}${e.amount} ${e.unit.name} ${e.food.name} \n`
|
|
}
|
|
}
|
|
|
|
return text
|
|
}
|
|
},
|
|
mounted: function () {
|
|
this.loadShoppingList()
|
|
|
|
{% if recipes %}
|
|
this.loading = true
|
|
this.edit_mode = true
|
|
let loadingRecipes = []
|
|
{% for r in recipes %}
|
|
loadingRecipes.push(this.loadInitialRecipe({{ r.recipe }}, {{ r.servings }}))
|
|
{% endfor %}
|
|
|
|
Promise.allSettled(loadingRecipes).then(() => {
|
|
this.loading = false
|
|
})
|
|
{% endif %}
|
|
|
|
{% if request.user.userpreference.shopping_auto_sync > 0 %}
|
|
setInterval(() => {
|
|
if ((this.shopping_list_id !== null) && !this.edit_mode && window.navigator.onLine) {
|
|
this.loadShoppingList(true)
|
|
}
|
|
}, {% widthratio request.user.userpreference.shopping_auto_sync 1 1000 %})
|
|
|
|
window.addEventListener('online', this.updateOnlineStatus);
|
|
window.addEventListener('offline', this.updateOnlineStatus);
|
|
{% endif %}
|
|
|
|
this.searchUsers('')
|
|
this.searchSupermarket('')
|
|
this.searchUnits('')
|
|
this.searchFoods('')
|
|
},
|
|
methods: {
|
|
findMergeEntry: function (categories, entry) {
|
|
for (let [i, e] of Object.entries(categories)) {
|
|
let found_entry = e.entries.find(item => {
|
|
if (entry.food.id === item.food.id) {
|
|
if (entry.unit === null && item.unit === null) {
|
|
return true
|
|
} else if (entry.unit !== null && item.unit !== null && entry.unit.id === item.unit.id) {
|
|
return true
|
|
}
|
|
}
|
|
})
|
|
|
|
if (found_entry !== undefined) {
|
|
return found_entry
|
|
}
|
|
}
|
|
return undefined
|
|
},
|
|
updateOnlineStatus(e) {
|
|
const {
|
|
type
|
|
} = e;
|
|
this.onLine = type === 'online';
|
|
},
|
|
makeToast: function (title, message, variant = null) {
|
|
//TODO remove duplicate function in favor of central one
|
|
this.$bvToast.toast(message, {
|
|
title: title,
|
|
variant: variant,
|
|
toaster: 'b-toaster-top-center',
|
|
solid: true
|
|
})
|
|
},
|
|
loadInitialRecipe: function (recipe, servings) {
|
|
return this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe)).then((response) => {
|
|
this.addRecipeToList(response.data, servings)
|
|
}).catch((err) => {
|
|
console.log("getRecipes error: ", err);
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
loadShoppingList: function (autosync = false) {
|
|
|
|
if (this.shopping_list_id) {
|
|
this.$http.get("{% url 'api:shoppinglist-detail' 123456 %}".replace('123456', this.shopping_list_id) + ((autosync) ? '?autosync=true' : '')).then((response) => {
|
|
if (!autosync) {
|
|
this.shopping_list = response.body
|
|
this.loading = false
|
|
} else {
|
|
let check_map = {}
|
|
for (let e of response.body.entries) {
|
|
check_map[e.id] = {checked: e.checked}
|
|
}
|
|
|
|
for (let se of this.shopping_list.entries) {
|
|
if (check_map[se.id] !== undefined) {
|
|
se.checked = check_map[se.id].checked
|
|
}
|
|
}
|
|
}
|
|
if (this.shopping_list.entries.length === 0) {
|
|
this.edit_mode = true
|
|
}
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
} else {
|
|
this.shopping_list = {
|
|
"recipes": [],
|
|
"entries": [],
|
|
"entries_display": [],
|
|
"shared": [{% for u in request.user.userpreference.plan_share.all %}
|
|
{'id': {{ u.pk }}, 'username': '{{ u.get_user_name }}'},
|
|
{% endfor %}],
|
|
"created_by": {{ request.user.pk }},
|
|
"supermarket": null
|
|
}
|
|
this.loading = false
|
|
|
|
if (this.shopping_list.entries.length === 0) {
|
|
this.edit_mode = true
|
|
}
|
|
}
|
|
},
|
|
updateShoppingList: function () {
|
|
this.loading = true
|
|
let recipe_promises = []
|
|
|
|
for (let i in this.shopping_list.recipes) {
|
|
if (this.shopping_list.recipes[i].created) {
|
|
console.log('updating recipe', this.shopping_list.recipes[i])
|
|
recipe_promises.push(this.$http.post("{% url 'api:shoppinglistrecipe-list' %}", this.shopping_list.recipes[i], {}).then((response) => {
|
|
let old_id = this.shopping_list.recipes[i].id
|
|
console.log("list recipe create respose ", response.body)
|
|
this.$set(this.shopping_list.recipes, i, response.body)
|
|
for (let e of this.shopping_list.entries.filter(item => item.list_recipe === old_id)) {
|
|
console.log("found recipe updating ID")
|
|
e.list_recipe = this.shopping_list.recipes[i].id
|
|
}
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
|
}))
|
|
}
|
|
}
|
|
|
|
Promise.allSettled(recipe_promises).then(() => {
|
|
console.log("proceeding to update shopping list", this.shopping_list)
|
|
|
|
if (this.shopping_list_id === null) {
|
|
return this.$http.post("{% url 'api:shoppinglist-list' %}", this.shopping_list, {}).then((response) => {
|
|
console.log(response)
|
|
this.makeToast(gettext('Updated'), gettext('Object created successfully!'), 'success')
|
|
this.loading = false
|
|
|
|
this.shopping_list = response.body
|
|
this.shopping_list_id = this.shopping_list.id
|
|
|
|
window.history.pushState('shopping_list', '{% trans 'Shopping List' %}', "{% url 'view_shopping' 123456 %}".replace('123456', this.shopping_list_id));
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast(gettext('Error'), '{% trans 'There was an error creating a resource!' %}' + err.bodyText, 'danger')
|
|
this.loading = false
|
|
})
|
|
} else {
|
|
return this.$http.put("{% url 'api:shoppinglist-detail' shopping_list_id %}", this.shopping_list, {}).then((response) => {
|
|
console.log(response)
|
|
this.shopping_list = response.body
|
|
this.makeToast(gettext('Updated'), gettext('Changes saved successfully!'), 'success')
|
|
this.loading = false
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
|
this.loading = false
|
|
})
|
|
}
|
|
|
|
|
|
})
|
|
},
|
|
sortEntries: function (a, b) {
|
|
//TODO implement me (might be difficult because of computed drag changed stuff)
|
|
},
|
|
dragChanged: function (category, evt) {
|
|
if (evt.added !== undefined) {
|
|
if (evt.added.element.id === undefined) {
|
|
this.makeToast(gettext('Warning'), gettext('This feature is only available after saving the shopping list'), 'warning')
|
|
} else {
|
|
this.shopping_list.entries.forEach(entry => {
|
|
if (entry.id === evt.added.element.id) {
|
|
if (category.id === -1) {
|
|
entry.food.supermarket_category = null
|
|
} else {
|
|
entry.food.supermarket_category = {
|
|
name: category.name,
|
|
id: category.id
|
|
}
|
|
}
|
|
this.$http.put(("{% url 'api:food-detail' 123456 %}").replace('123456', entry.food.id), entry.food).then((response) => {
|
|
|
|
}).catch((err) => {
|
|
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
}
|
|
},
|
|
entryChecked: function (entry) {
|
|
this.shopping_list.entries.forEach((item) => {
|
|
if (entry.entries.includes(item.id)) {
|
|
item.checked = entry.checked
|
|
this.$http.put("{% url 'api:shoppinglistentry-detail' 123456 %}".replace('123456', item.id), item, {}).then((response) => {
|
|
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
|
this.loading = false
|
|
})
|
|
}
|
|
})
|
|
},
|
|
addEntry: function () {
|
|
if (this.new_entry.food !== undefined) {
|
|
this.shopping_list.entries.push({
|
|
'list_recipe': null,
|
|
'food': this.new_entry.food,
|
|
'unit': this.new_entry.unit,
|
|
'amount': parseFloat(this.new_entry.amount),
|
|
'order': 0,
|
|
'checked': false,
|
|
})
|
|
|
|
this.new_entry = {
|
|
unit: undefined,
|
|
amount: undefined,
|
|
food: undefined,
|
|
}
|
|
|
|
this.$refs.new_entry_amount.focus();
|
|
} else {
|
|
this.makeToast(gettext('Error'), gettext('Please enter a valid food'), 'danger')
|
|
}
|
|
},
|
|
getRecipes: function () {
|
|
let url = "{% url 'api:recipe-list' %}?limit=5&internal=true"
|
|
if (this.recipe_query !== '') {
|
|
url += '&query=' + this.recipe_query;
|
|
} else {
|
|
this.recipes = []
|
|
return
|
|
}
|
|
|
|
this.$http.get(url).then((response) => {
|
|
this.recipes = response.data;
|
|
}).catch((err) => {
|
|
console.log("getRecipes error: ", err);
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
getRecipeUrl: function (id) { //TODO generic function that can be reused else were
|
|
return '{% url 'view_recipe' 123456 %}'.replace('123456', id)
|
|
},
|
|
addRecipeToList: function (recipe, servings = 1) {
|
|
let slr = {
|
|
"created": true,
|
|
"id": Math.random() * 1000,
|
|
"recipe": recipe.id,
|
|
"recipe_name": recipe.name,
|
|
"servings": servings,
|
|
}
|
|
this.shopping_list.recipes.push(slr)
|
|
|
|
this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe.id)).then((response) => {
|
|
for (let s of response.data.steps) {
|
|
for (let i of s.ingredients) {
|
|
if (!i.is_header && i.food !== null && i.food.ignore_shopping === false) {
|
|
this.shopping_list.entries.push({
|
|
'list_recipe': slr.id,
|
|
'food': i.food,
|
|
'unit': i.unit,
|
|
'amount': i.amount,
|
|
'order': 0
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}).catch((err) => {
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
removeRecipeFromList: function (slr) {
|
|
this.shopping_list.entries = this.shopping_list.entries.filter(item => item.list_recipe !== slr.id)
|
|
this.shopping_list.recipes = this.shopping_list.recipes.filter(item => item !== slr)
|
|
},
|
|
searchKeywords: function (query) {
|
|
this.keywords_loading = true
|
|
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
|
this.keywords = response.data;
|
|
this.keywords_loading = false
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
|
|
searchUnits: function (query) { //TODO move to central component
|
|
this.units_loading = true
|
|
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
|
this.units = response.data;
|
|
this.units_loading = false
|
|
}).catch((err) => {
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
searchFoods: function (query) { //TODO move to central component
|
|
this.foods_loading = true
|
|
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
|
this.foods = response.data
|
|
this.foods_loading = false
|
|
}).catch((err) => {
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
addFoodType: function (tag, index) { //TODO move to central component
|
|
let new_food = {'name': tag, supermarket_category: null}
|
|
this.foods.push(new_food)
|
|
this.new_entry.food = new_food
|
|
},
|
|
addUnitType: function (tag, index) { //TODO move to central component
|
|
let new_unit = {'name': tag}
|
|
this.units.push(new_unit)
|
|
this.new_entry.unit = new_unit
|
|
},
|
|
searchUsers: function (query) { //TODO move to central component
|
|
this.users_loading = true
|
|
this.$http.get("{% url 'api:username-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
|
this.users = response.data
|
|
this.users_loading = false
|
|
}).catch((err) => {
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
searchSupermarket: function (query) { //TODO move to central component
|
|
this.supermarkets_loading = true
|
|
this.$http.get("{% url 'api:supermarket-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
|
this.supermarkets = response.data
|
|
this.supermarkets_loading = false
|
|
}).catch((err) => {
|
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
|
|
|
|
},
|
|
beforeDestroy() {
|
|
window.removeEventListener('online', this.updateOnlineStatus);
|
|
window.removeEventListener('offline', this.updateOnlineStatus);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{% endblock %} |