TandoorRecipes/vue/src/components/ShoppingLineItem.vue
2023-12-27 21:15:24 +01:00

262 lines
9.5 KiB
Vue

<template>
<div id="shopping_line_item">
<b-button-group class="w-100" v-if="(useShoppingListStore().show_checked_entries || !is_checked) && !is_delayed">
<b-button :class="{'btn-dark': (!is_checked && !is_delayed), 'btn-success': is_checked, 'btn-warning': is_delayed}" block class="btn btn-block text-left" @click="detail_modal_visible = true">
<div class="d-flex ">
<div class="d-flex flex-column pr-2" v-if="Object.keys(amounts).length> 0">
<span v-for="a in amounts" v-bind:key="a.id">{{ a.amount }} {{ a.unit }}<br/></span>
</div>
<div class="d-flex flex-column flex-grow-1 align-self-center">
{{ food.name }}
</div>
</div>
<span v-if="info_row"><small class="text-muted">{{ info_row }}</small></span>
</b-button>
<b-button variant="success" @click="useShoppingListStore().setEntriesCheckedState(entries, !is_checked)" :class="{'btn-success': !is_checked, 'btn-warning': is_checked}">
<i class="fas" :class="{'fa-check': !is_checked, 'fa-times': is_checked}"></i>
</b-button>
</b-button-group>
<b-modal v-model="detail_modal_visible" @hidden="detail_modal_visible = false">
<template #modal-title>
<h5> {{ food_row }}</h5>
<small class="text-muted">{{ food.description }}</small>
</template>
<template #default>
<h5 class="mt-2">{{ $t('Quick actions') }}</h5>
{{ $t('Category')}}
<b-form-select
class="form-control mb-2"
:options="useShoppingListStore().supermarket_categories"
text-field="name"
value-field="id"
v-model="food.supermarket_category"
@change="detail_modal_visible = false; updateFoodCategory(food)"
></b-form-select>
<!-- TODO implement -->
<!-- <b-button variant="success" block @click="detail_modal_visible = false;"> {{ $t("Edit_Food") }}</b-button> -->
<b-button variant="info" block @click="detail_modal_visible = false;useShoppingListStore().delayEntries(entries)">{{ $t('Shop_later') }}</b-button>
<h6 class="mt-2">{{ $t('Entries') }}</h6>
<b-button variant="danger" block @click="detail_modal_visible = false;useShoppingListStore().deleteEntries(entries)">{{ $t('Delete_All') }}</b-button>
<b-row v-for="e in entries" v-bind:key="e.id">
<b-col cold="12">
<b-button-group class="mt-1 w-100">
<b-button variant="dark" block class="btn btn-block text-left">
<span>{{ food.name }}</span>
<span><br/><small class="text-muted">
<span v-if="e.recipe_mealplan && e.recipe_mealplan.recipe_name !== ''">
<a :href="resolveDjangoUrl('view_recipe', e.recipe_mealplan.recipe)"> {{ e.recipe_mealplan.recipe_name }} </a>({{
e.recipe_mealplan.servings
}} {{ $t('Servings') }})<br/>
</span>
<span
v-if="e.recipe_mealplan && e.recipe_mealplan.mealplan_type !== undefined"> {{
e.recipe_mealplan.mealplan_type
}} {{ formatDate(e.recipe_mealplan.mealplan_from_date) }} <br/></span>
{{ e.created_by.display_name }} {{ formatDate(e.created_at) }}<br/>
</small></span>
</b-button>
<b-button variant="warning" @click="detail_modal_visible = false; useShoppingListStore().deleteObject(e)"><i class="fas fa-trash"></i></b-button> <!-- TODO implement -->
</b-button-group>
</b-col>
</b-row>
</template>
<template #modal-footer>
<span></span>
</template>
</b-modal>
</div>
</template>
<script>
import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils"
import {useMealPlanStore} from "@/stores/MealPlanStore";
import {useShoppingListStore} from "@/stores/ShoppingListStore";
import {ApiApiFactory} from "@/utils/openapi/api";
Vue.use(BootstrapVue)
export default {
name: "ShoppingLineItem",
mixins: [ApiMixin],
components: {},
props: {
entries: {type: Object,},
},
data() {
return {
detail_modal_visible: false,
}
},
computed: {
is_checked: function () {
for (let i in this.entries) {
if (!this.entries[i].checked) {
return false
}
}
return true
},
is_delayed: function () {
for (let i in this.entries) {
if (Date.parse(this.entries[i].delay_until) > new Date(Date.now())) {
return true
}
}
return false
},
food: function () {
return this.entries[Object.keys(this.entries)[0]]['food']
},
amounts: function () {
let unit_amounts = {}
for (let i in this.entries) {
let e = this.entries[i]
let unit = -1
if (e.unit !== undefined && e.unit !== null) {
unit = e.unit.id
}
if (e.amount > 0) {
if (unit in unit_amounts) {
unit_amounts[unit]['amount'] += e.amount
} else {
if (unit === -1) {
unit_amounts[unit] = {id: -1, unit: "", amount: e.amount}
} else {
unit_amounts[unit] = {id: e.unit.id, unit: e.unit.name, amount: e.amount}
}
}
}
}
return unit_amounts
},
food_row: function () {
return this.food.name
},
info_row: function () {
// TODO add setting
let info_row = []
let display_authors = false
let display_recipes = true
let display_mealplans = true
let authors = []
let recipes = []
let meal_pans = []
for (let i in this.entries) {
let e = this.entries[i]
if (authors.indexOf(e.created_by.display_name) === -1) {
authors.push(e.created_by.display_name)
}
if (e.recipe_mealplan !== null) {
let recipe_name = e.recipe_mealplan.recipe_name
if (recipes.indexOf(recipe_name) === -1) {
recipes.push(recipe_name)
}
if ('mealplan_from_date' in e.recipe_mealplan) {
let meal_plan_entry = (e?.recipe_mealplan?.mealplan_type || '') + ' (' + this.formatDate(e.recipe_mealplan.mealplan_from_date) + ')'
if (meal_pans.indexOf(meal_plan_entry) === -1) {
meal_pans.push(meal_plan_entry)
}
}
}
if (recipes.length > 1) {
let short_recipes = []
recipes.forEach(r => {
short_recipes.push(r.substring(0, 14) + (r.length > 14 ? '..' : ''))
})
recipes = short_recipes
}
}
if (display_authors && authors.length > 0) {
info_row.push(authors.join(', '))
}
if (display_recipes && recipes.length > 0) {
info_row.push(recipes.join(', '))
}
if (display_mealplans && meal_pans.length > 0) {
info_row.push(meal_pans.join(', '))
}
return info_row.join(' - ')
}
},
watch: {},
mounted() {
},
methods: {
useShoppingListStore,
resolveDjangoUrl,
formatDate: function (datetime) {
if (!datetime) {
return
}
return Intl.DateTimeFormat(window.navigator.language, {
dateStyle: "short",
}).format(Date.parse(datetime))
},
updateFoodCategory: function (food) {
if (typeof food.supermarket_category === "number") { // not the best solution, but as long as generic multiselect does not support caching, I don't want to use a proper model
food.supermarket_category = this.useShoppingListStore().supermarket_categories.filter(sc => sc.id === food.supermarket_category)[0]
}
let apiClient = new ApiApiFactory()
apiClient.updateFood(food.id, food).then(r => {
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
},
}
</script>
<style>
</style>