380 lines
16 KiB
HTML
380 lines
16 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>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="row">
|
|
<div class="col col-9">
|
|
<h2>{% trans 'Shopping List' %}</h2>
|
|
</div>
|
|
<div class="col col-3">
|
|
<b-form-checkbox switch size="lg" v-model="edit_mode">{% trans 'Edit' %}</b-form-checkbox>
|
|
</div>
|
|
</div>
|
|
|
|
<template v-if="shopping_list !== undefined">
|
|
|
|
|
|
<div v-if="edit_mode">
|
|
<div class="row">
|
|
<div class="col col-md-6">
|
|
<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">[[x.name]]
|
|
<button @click="addRecipeToList(x)"><i class="fa fa-plus"></i></button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="col col-md-6">
|
|
<ul class="list-group" style="margin-top: 8px">
|
|
<li class="list-group-item" v-for="x in shopping_list.recipes">[[x.recipe.name]] <input
|
|
type="number" v-model="x.multiplier" @change="updateDisplay()">
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col col-md-12">
|
|
<input class="form-control form-control-sm" type="number" placeholder="{% trans 'Amount' %}"
|
|
v-model="new_entry.amount" ref="new_entry_amount">
|
|
<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>
|
|
|
|
<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>
|
|
<button class="btn btn-success btn-sm" @click="addEntry()"><i class="fa fa-plus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" style="margin-top: 8px">
|
|
<div class="col col-12">
|
|
<table class="table table-sm table-striped">
|
|
<tr v-for="x in shopping_list.entries_display">
|
|
<td>[[x.amount]]</td>
|
|
<td>[[x.unit.name]]</td>
|
|
<td>[[x.food.name]]</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
Non Edit
|
|
</div>
|
|
|
|
<button @click="updateShoppingList()">Save</button>
|
|
</template>
|
|
|
|
|
|
{% endblock %}
|
|
{% block 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: {{ shopping_list_id }},
|
|
edit_mode: true,
|
|
recipe_query: '',
|
|
recipes: [],
|
|
shopping_list: undefined,
|
|
multiplier_cache: {},
|
|
new_entry: {
|
|
unit: undefined,
|
|
amount: undefined,
|
|
food: undefined,
|
|
},
|
|
foods: [],
|
|
foods_loading: false,
|
|
units: [],
|
|
units_loading: false,
|
|
},
|
|
directives: {
|
|
tabindex: {
|
|
inserted(el) {
|
|
el.setAttribute('tabindex', 0);
|
|
}
|
|
}
|
|
},
|
|
/*
|
|
watch: {
|
|
recipe: {
|
|
deep: true,
|
|
handler() {
|
|
this.recipe_changed = this.recipe_changed !== undefined;
|
|
}
|
|
}
|
|
},
|
|
|
|
created() {
|
|
window.addEventListener('beforeunload', this.warnPageLeave)
|
|
},
|
|
*/
|
|
mounted: function () {
|
|
|
|
|
|
if (this.shopping_list_id) {
|
|
this.loadShoppingList()
|
|
} else {
|
|
//TODO default share users
|
|
this.shopping_list = {
|
|
"recipes": [],
|
|
"entries": [],
|
|
"entries_display": [],
|
|
"shared": [],
|
|
"created_by": 1
|
|
}
|
|
|
|
this.updateDisplay()
|
|
}
|
|
|
|
},
|
|
methods: {
|
|
/*
|
|
warnPageLeave: function (event) {
|
|
if (this.recipe_changed) {
|
|
event.returnValue = ''
|
|
return ''
|
|
}
|
|
},
|
|
*/
|
|
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
|
|
})
|
|
},
|
|
loadShoppingList: function () {
|
|
|
|
this.$http.get("{% url 'api:shoppinglist-detail' shopping_list_id %}").then((response) => {
|
|
this.shopping_list = response.body
|
|
this.updateDisplay()
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
updateShoppingList: function () {
|
|
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.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('{% trans 'Error' %}', '{% trans '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)
|
|
|
|
this.$http.put("{% url 'api:shoppinglist-detail' shopping_list_id %}", this.shopping_list, {}).then((response) => {
|
|
console.log(response)
|
|
this.makeToast('{% trans 'Updated' %}', '{% trans 'Changes saved successfully!' %}', 'success')
|
|
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error updating a resource!' %}' + err.bodyText, 'danger')
|
|
})
|
|
})
|
|
},
|
|
addEntry: function () {
|
|
this.shopping_list.entries.push({
|
|
'list_recipe': null,
|
|
'food': this.new_entry.food,
|
|
'unit': ((this.new_entry.unit !== undefined) ? this.new_entry.unit : {'name': ''}),
|
|
'amount': parseFloat(this.new_entry.amount),
|
|
'order': 0
|
|
})
|
|
|
|
this.new_entry = {
|
|
unit: undefined,
|
|
amount: undefined,
|
|
food: undefined,
|
|
}
|
|
|
|
this.$refs.new_entry_amount.focus();
|
|
this.updateDisplay()
|
|
},
|
|
getRecipes: function () {
|
|
let url = "{% url 'api:recipe-list' %}?limit=5"
|
|
if (this.recipe_query !== '') {
|
|
url += '&query=' + this.recipe_query;
|
|
}
|
|
|
|
this.$http.get(url).then((response) => {
|
|
this.recipes = response.data;
|
|
}).catch((err) => {
|
|
console.log("getRecipes error: ", err);
|
|
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
addRecipeToList: function (recipe) {
|
|
console.log(this.shopping_list)
|
|
|
|
let slr = {
|
|
"created": true,
|
|
"id": Math.random() * 1000,
|
|
"recipe": recipe.id,
|
|
"multiplier": 1
|
|
}
|
|
this.shopping_list.recipes.push(slr)
|
|
|
|
for (let s of recipe.steps) {
|
|
for (let i of s.ingredients) {
|
|
|
|
if (!i.is_header) {
|
|
this.shopping_list.entries.push({
|
|
'list_recipe': slr.id,
|
|
'food': i.food,
|
|
'unit': ((i.unit !== undefined) ? i.unit : {'name': ''}),
|
|
'amount': i.amount,
|
|
'order': 0
|
|
})
|
|
}
|
|
}
|
|
}
|
|
this.updateDisplay()
|
|
},
|
|
updateMultiplierCache: function () {
|
|
this.multiplier_cache = {}
|
|
for (let r of this.shopping_list.recipes) {
|
|
this.multiplier_cache[r.id] = r.multiplier
|
|
}
|
|
},
|
|
updateDisplay: function () {
|
|
this.updateMultiplierCache()
|
|
|
|
//TODO merge multiple ingredients of same unit
|
|
this.shopping_list.entries_display = []
|
|
|
|
this.shopping_list.entries.forEach(function (element) {
|
|
let item = {}
|
|
Object.assign(item, element);
|
|
if (item.list_recipe !== null) {
|
|
item.amount = item.amount * app.multiplier_cache[item.list_recipe]
|
|
}
|
|
app.shopping_list.entries_display.push(item)
|
|
});
|
|
},
|
|
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('{% trans 'Error' %}', '{% trans '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('{% trans 'Error' %}', '{% trans '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('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
|
})
|
|
},
|
|
addFoodType: function (tag, index) { //TODO move to central component
|
|
let new_food = {'name': tag}
|
|
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
|
|
},
|
|
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{% endblock %} |