Fix after rebase

This commit is contained in:
smilerz
2021-10-28 07:35:30 -05:00
parent f16e457d14
commit 10a33add75
73 changed files with 5579 additions and 2524 deletions

View File

@@ -0,0 +1,122 @@
<template>
<div>
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Manage_Books')" :ok-title="$t('Add')"
:cancel-title="$t('Close')" @ok="addToBook()" @shown="loadBookEntries">
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center" v-for="be in this.recipe_book_list" v-bind:key="be.id">
{{ be.book_content.name }} <span class="btn btn-sm btn-danger" @click="removeFromBook(be)"><i class="fa fa-trash-alt"></i></span>
</li>
</ul>
<multiselect
style="margin-top: 1vh"
v-model="selected_book"
:options="books_filtered"
:taggable="true"
@tag="createBook"
v-bind:tag-placeholder="$t('Create')"
:placeholder="$t('Select_Book')"
label="name"
track-by="id"
id="id_books"
:multiple="false"
:loading="books_loading"
@search-change="loadBooks">
</multiselect>
</b-modal>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
import moment from 'moment'
Vue.prototype.moment = moment
import Vue from "vue";
import {BootstrapVue} from "bootstrap-vue";
import {ApiApiFactory} from "@/utils/openapi/api";
import {makeStandardToast, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue)
export default {
name: 'AddRecipeToBook',
components: {
Multiselect
},
props: {
recipe: Object,
modal_id: Number
},
data() {
return {
books: [],
books_loading: false,
recipe_book_list: [],
selected_book: null,
}
},
computed: {
books_filtered: function () {
let books_filtered = []
this.books.forEach(b => {
if (this.recipe_book_list.filter(e => e.book === b.id).length === 0) {
books_filtered.push(b)
}
})
return books_filtered
}
},
mounted() {
},
methods: {
loadBooks: function (query) {
this.books_loading = true
let apiFactory = new ApiApiFactory()
apiFactory.listRecipeBooks({query: {query: query}}).then(results => {
this.books = results.data.filter(e => this.recipe_book_list.indexOf(e) === -1)
this.books_loading = false
})
},
createBook: function (name) {
let apiFactory = new ApiApiFactory()
apiFactory.createRecipeBook({name: name}).then(r => {
this.books.push(r.data)
this.selected_book = r.data
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
})
},
addToBook: function () {
let apiFactory = new ApiApiFactory()
apiFactory.createRecipeBookEntry({book: this.selected_book.id, recipe: this.recipe.id}).then(r => {
this.recipe_book_list.push(r.data)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
})
},
removeFromBook: function (book_entry) {
let apiFactory = new ApiApiFactory()
apiFactory.destroyRecipeBookEntry(book_entry.id).then(r => {
this.recipe_book_list = this.recipe_book_list.filter(e => e.id !== book_entry.id)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
})
},
loadBookEntries: function () {
let apiFactory = new ApiApiFactory()
apiFactory.listRecipeBookEntrys({query: {recipe: this.recipe.id}}).then(r => {
this.recipe_book_list = r.data
this.loadBooks('')
})
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

View File

@@ -28,7 +28,7 @@
<script>
import Vue from "vue"
import { BootstrapVue } from "bootstrap-vue"
import { getForm } from "@/utils/utils"
import { getForm, formFunctions } from "@/utils/utils"
Vue.use(BootstrapVue)
@@ -84,6 +84,10 @@ export default {
show: function () {
if (this.show) {
this.form = getForm(this.model, this.action, this.item1, this.item2)
// TODO: I don't know how to generalize this, but Food needs default values to drive inheritance
if (this.form?.form_function) {
this.form = formFunctions[this.form.form_function](this.form)
}
this.dirty = true
this.$bvModal.show("modal_" + this.id)
} else {

View File

@@ -0,0 +1,219 @@
<template>
<b-modal :id="modal_id" size="lg" :title="modal_title" hide-footer aria-label="">
<div class="row">
<div class="col col-md-12">
<div class="row">
<div class="col-6 col-lg-9">
<b-input-group>
<b-form-input
id="TitleInput"
v-model="entryEditing.title"
:placeholder="entryEditing.title_placeholder"
@change="missing_recipe = false"
></b-form-input>
<b-input-group-append class="d-none d-lg-block">
<b-button variant="primary" @click="entryEditing.title = ''"
><i class="fa fa-eraser"></i
></b-button>
</b-input-group-append>
</b-input-group>
<span class="text-danger" v-if="missing_recipe">{{ $t("Title_or_Recipe_Required") }}</span>
<small tabindex="-1" class="form-text text-muted" v-if="!missing_recipe">{{
$t("Title")
}}</small>
</div>
<div class="col-6 col-lg-3">
<input type="date" id="DateInput" class="form-control" v-model="entryEditing.date" />
<small tabindex="-1" class="form-text text-muted">{{ $t("Date") }}</small>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-lg-6 col-xl-6">
<b-form-group>
<generic-multiselect
@change="selectRecipe"
:initial_selection="entryEditing_initial_recipe"
:label="'name'"
:model="Models.RECIPE"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
v-bind:placeholder="$t('Recipe')"
:limit="10"
:multiple="false"
></generic-multiselect>
<small tabindex="-1" class="form-text text-muted">{{ $t("Recipe") }}</small>
</b-form-group>
<b-form-group class="mt-3">
<generic-multiselect
required
@change="selectMealType"
:label="'name'"
:model="Models.MEAL_TYPE"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
v-bind:placeholder="$t('Meal_Type')"
:limit="10"
:multiple="false"
:initial_selection="entryEditing_initial_meal_type"
:allow_create="true"
:create_placeholder="$t('Create_New_Meal_Type')"
@new="createMealType"
></generic-multiselect>
<span class="text-danger" v-if="missing_meal_type">{{ $t("Meal_Type_Required") }}</span>
<small tabindex="-1" class="form-text text-muted" v-if="!missing_meal_type">{{
$t("Meal_Type")
}}</small>
</b-form-group>
<b-form-group label-for="NoteInput" :description="$t('Note')" class="mt-3">
<textarea
class="form-control"
id="NoteInput"
v-model="entryEditing.note"
:placeholder="$t('Note')"
></textarea>
</b-form-group>
<b-input-group>
<b-form-input
id="ServingsInput"
v-model="entryEditing.servings"
:placeholder="$t('Servings')"
></b-form-input>
</b-input-group>
<small tabindex="-1" class="form-text text-muted">{{ $t("Servings") }}</small>
<!-- TODO: hide this checkbox if autoadding menuplans, but allow editing on-hand -->
<b-input-group v-if="!autoMealPlan">
<b-form-checkbox id="AddToShopping" v-model="entryEditing.addshopping" />
<small tabindex="-1" class="form-text text-muted">{{ $t("AddToShopping") }}</small>
</b-input-group>
</div>
<div class="col-lg-6 d-none d-lg-block d-xl-block">
<recipe-card :recipe="entryEditing.recipe" v-if="entryEditing.recipe != null"></recipe-card>
</div>
</div>
<div class="row mt-3 mb-3">
<div class="col-12">
<b-button variant="danger" @click="deleteEntry" v-if="allow_delete"
>{{ $t("Delete") }}
</b-button>
<b-button class="float-right" variant="primary" @click="editEntry">{{ $t("Save") }}</b-button>
</div>
</div>
</div>
</div>
</b-modal>
</template>
<script>
import Vue from "vue"
import { BootstrapVue } from "bootstrap-vue"
import GenericMultiselect from "@/components/GenericMultiselect"
import { ApiMixin, getUserPreference } from "@/utils/utils"
const { ApiApiFactory } = require("@/utils/openapi/api")
const { StandardToasts } = require("@/utils/utils")
Vue.use(BootstrapVue)
export default {
name: "MealPlanEditModal",
props: {
entry: Object,
entryEditing_initial_recipe: Array,
entryEditing_initial_meal_type: Array,
modal_title: String,
modal_id: {
type: String,
default: "edit-modal",
},
allow_delete: {
type: Boolean,
default: true,
},
},
mixins: [ApiMixin],
components: {
GenericMultiselect,
RecipeCard: () => import("@/components/RecipeCard.vue"),
},
data() {
return {
entryEditing: {},
missing_recipe: false,
missing_meal_type: false,
}
},
watch: {
entry: {
handler() {
this.entryEditing = Object.assign({}, this.entry)
},
deep: true,
},
},
mounted: function() {},
computed: {
autoMealPlan: function() {
return getUserPreference("mealplan_autoadd_shopping")
},
},
methods: {
editEntry() {
this.missing_meal_type = false
this.missing_recipe = false
let cancel = false
if (this.entryEditing.meal_type == null) {
this.missing_meal_type = true
cancel = true
}
if (this.entryEditing.recipe == null && this.entryEditing.title === "") {
this.missing_recipe = true
cancel = true
}
if (!cancel) {
this.$bvModal.hide(`edit-modal`)
this.$emit("save-entry", this.entryEditing)
}
},
deleteEntry() {
this.$bvModal.hide(`edit-modal`)
this.$emit("delete-entry", this.entryEditing)
},
selectMealType(event) {
this.missing_meal_type = false
if (event.val != null) {
this.entryEditing.meal_type = event.val
} else {
this.entryEditing.meal_type = null
}
},
createMealType(event) {
if (event != "") {
let apiClient = new ApiApiFactory()
apiClient
.createMealType({ name: event })
.then((e) => {
this.$emit("reload-meal-types")
})
.catch((error) => {
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
})
}
},
selectRecipe(event) {
console.log(event, this.entryEditing)
this.missing_recipe = false
if (event.val != null) {
this.entryEditing.recipe = event.val
this.entryEditing.title_placeholder = this.entryEditing.recipe.name
this.entryEditing.servings = this.entryEditing.recipe.servings
} else {
this.entryEditing.recipe = null
this.entryEditing.title_placeholder = ""
this.entryEditing.servings = 1
}
},
},
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,158 @@
<template>
<div>
<b-modal :id="`shopping_${this.modal_id}`" hide-footer @show="loadRecipe">
<template v-slot:modal-title><h4>{{$t('Add_Servings_to_Shopping',{'servings': servings})}}</h4></template>
<loading-spinner v-if="loading"></loading-spinner>
<div class="accordion" role="tablist" v-if="!loading">
<b-card no-body class="mb-1">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block v-b-toggle.accordion-0 class="text-left" variant="outline-info">{{recipe.name}}</b-button>
</b-card-header>
<b-collapse id="accordion-0" visible accordion="my-accordion" role="tabpanel">
<ingredients-card
:steps="steps"
:recipe="recipe.id"
:ingredient_factor="ingredient_factor"
:servings="servings"
:show_shopping="true"
:add_shopping_mode="true"
:header="false"
@add-to-shopping="addShopping($event)"
/>
</b-collapse>
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
<template v-for="r in related_recipes">
<b-card no-body class="mb-1" :key="r.recipe.id">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button btn-sm block v-b-toggle="'accordion-' +r.recipe.id" class="text-left" variant="outline-primary">{{r.recipe.name}}</b-button>
</b-card-header>
<b-collapse :id="'accordion-'+r.recipe.id" accordion="my-accordion" role="tabpanel">
<ingredients-card
:steps="r.steps"
:recipe="r.recipe.id"
:ingredient_factor="ingredient_factor"
:servings="servings"
:show_shopping="true"
:add_shopping_mode="true"
:header="false"
@add-to-shopping="addShopping($event)"
/>
</b-collapse>
</b-card>
</template>
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
</b-card>
</div>
<div class="row mt-3 mb-3">
<div class="col-12 text-right">
<b-button class="mx-2" variant="secondary" @click="$bvModal.hide(`shopping_${modal_id}`)">{{ $t('Cancel') }}
</b-button>
<b-button class="mx-2" variant="success" @click="saveShopping">{{ $t('Save') }}
</b-button>
</div>
</div>
</b-modal>
</div>
</template>
<script>
import Vue from 'vue'
import {BootstrapVue} from 'bootstrap-vue'
Vue.use(BootstrapVue)
const {ApiApiFactory} = require("@/utils/openapi/api");
import {StandardToasts} from "@/utils/utils";
import IngredientsCard from "@/components/IngredientsCard";
import LoadingSpinner from "@/components/LoadingSpinner";
export default {
name: 'ShoppingModal',
components: {IngredientsCard, LoadingSpinner},
mixins: [],
props: {
recipe: {required: true, type: Object},
servings: {type: Number},
modal_id: {required: true, type: Number},
},
data() {
return {
loading: true,
steps: [],
recipe_servings: 0,
add_shopping: [],
related_recipes: []
}
},
mounted() {
},
computed: {
ingredient_factor: function () {
return this.servings / this.recipe.servings || this.recipe_servings
},
},
watch: {
},
methods: {
loadRecipe: function() {
this.add_shopping = []
this.related_recipes = []
let apiClient = new ApiApiFactory()
apiClient.retrieveRecipe(this.recipe.id).then((result) => {
this.steps = result.data.steps
// ALERT: this will all break if ingredients are re-used between recipes
// ALERT: this also doesn't quite work right if the same recipe appears multiple time in the related recipes
this.add_shopping = [...this.add_shopping, ...this.steps.map(x => x.ingredients).flat().map(x => x.id)]
this.recipe_servings = result.data?.servings
this.loading = false
})
// get a list of related recipes
apiClient.relatedRecipe(this.recipe.id).then((result) => {
return result.data
}).then((related_recipes) => {
let promises = []
related_recipes.forEach(x => {
promises.push(apiClient.listSteps(x.id).then((recipe_steps) => {
this.related_recipes.push({
'recipe': x,
'steps': recipe_steps.data.results.filter(x => x.ingredients.length > 0)
})
}))
})
return Promise.all(promises)
}).then(() => {
this.add_shopping = [
...this.add_shopping,
...this.related_recipes.map(x => x.steps).flat().map(x => x.ingredients).flat().map(x => x.id)
]
})
},
addShopping: function(e) {
if (e.add) {
this.add_shopping.push(e.item.id)
} else {
this.add_shopping = this.add_shopping.filter(x => x !== e.item.id)
}
},
saveShopping: function() {
// another choice would be to create ShoppingListRecipes for each recipe - this bundles all related recipe under the parent recipe
let shopping_recipe = {
'id': this.recipe.id,
'ingredients': this.add_shopping,
'servings': this.servings
}
let apiClient = new ApiApiFactory()
apiClient.shoppingRecipe(this.recipe.id, shopping_recipe).then((result) => {
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
}).catch((err) => {
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
})
this.$bvModal.hide(`shopping_${this.modal_id}`)
}
}
}
</script>