Basic editing of users ingredients
This commit is contained in:
parent
a4a4497dc4
commit
6a7dfbe460
3 changed files with 105 additions and 13 deletions
56
src/components/ingredients/EditableIngredientsPanel.vue
Normal file
56
src/components/ingredients/EditableIngredientsPanel.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="ingredient in ingredients" :key="ingredient.id">
|
||||||
|
<div v-if="editing">
|
||||||
|
<ingredient-line :ingredient="ingredient" @update="updateIngredient" @update-ingredient="updateIngredient" @update-product-link="updateProduct" />
|
||||||
|
<button @click="$emit('on-delete', ingredient)">
|
||||||
|
<img class="icon" :src="require('@/assets/trash.svg')" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<compact-parsed-ingredient :ingredient="ingredient" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button v-if="editing" @click="$emit('on-add')">Add Ingredient</button>
|
||||||
|
<button @click="editing = !editing">{{ editing ? 'Done' : 'Edit' }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import data from '@/data.js'
|
||||||
|
import IngredientLine from './IngredientLine.vue'
|
||||||
|
import CompactParsedIngredient from './CompactParsedIngredient.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EditableIngredientsPanel',
|
||||||
|
components: { IngredientLine, CompactParsedIngredient },
|
||||||
|
props: ['ingredients', 'editOnly'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editing: this.editOnly ?? false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateProduct(ingredient, product_link) {
|
||||||
|
const product = await data.parseProduct(ingredient, product_link);
|
||||||
|
this.$emit('on-update-ingredient', ingredient, { ...ingredient, product });
|
||||||
|
},
|
||||||
|
async updateIngredient(ingredient, line) {
|
||||||
|
const newIngredients = await data.parseIngredients([line]);
|
||||||
|
this.$emit('on-update-ingredient', ingredient, newIngredients[0]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -3,7 +3,11 @@
|
||||||
<h1>Shopping List</h1>
|
<h1>Shopping List</h1>
|
||||||
<h4>Included Meals</h4>
|
<h4>Included Meals</h4>
|
||||||
<meal-selection-list @meal-selected="mealSelected" @meal-unselected="mealUnselected" />
|
<meal-selection-list @meal-selected="mealSelected" @meal-unselected="mealUnselected" />
|
||||||
<ul>
|
<h4>My Requests</h4>
|
||||||
|
<editable-ingredients-panel @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" :ingredients="mySources" />
|
||||||
|
|
||||||
|
<h4>Full shopping list</h4>
|
||||||
|
<ul class="full-shopping-list">
|
||||||
<li v-for="item in shoppingList" :key="item.id">
|
<li v-for="item in shoppingList" :key="item.id">
|
||||||
<shopping-list-item :product="item.product" :sources="item.sources" />
|
<shopping-list-item :product="item.product" :sources="item.sources" />
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -38,7 +42,7 @@
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
li {
|
.full-shopping-list li {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
|
@ -46,28 +50,33 @@ li {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
.full-shopping-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import data from '@/data.js'
|
||||||
|
|
||||||
|
import EditableIngredientsPanel from '@/components/ingredients/EditableIngredientsPanel.vue'
|
||||||
import ShoppingListItem from './ShoppingListItem.vue'
|
import ShoppingListItem from './ShoppingListItem.vue'
|
||||||
import MealSelectionList from './MealSelectionList.vue'
|
import MealSelectionList from './MealSelectionList.vue'
|
||||||
|
|
||||||
import { mealToShoppingListSources, removeMealFromShoppingListSources, groupByProduct } from './shopping.js'
|
import { mealToShoppingListSources, removeMealFromShoppingListSources, groupByProduct } from './shopping.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ShoppingPage',
|
name: 'ShoppingPage',
|
||||||
components: {
|
components: { ShoppingListItem, MealSelectionList, EditableIngredientsPanel },
|
||||||
ShoppingListItem, MealSelectionList
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return { shoppingList: [], sources: [] }
|
return { shoppingList: [], sources: [], mySources: [] }
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
sources() {
|
async sources() {
|
||||||
this.shoppingList = groupByProduct(this.sources);
|
this.shoppingList = groupByProduct(this.sources);
|
||||||
|
|
||||||
|
const person = await data.currentUser();
|
||||||
|
this.mySources = this.sources.filter(source => source.person?.id === person?.id).map(source => source.ingredient);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -76,6 +85,25 @@ export default {
|
||||||
},
|
},
|
||||||
mealUnselected(meal) {
|
mealUnselected(meal) {
|
||||||
this.sources = removeMealFromShoppingListSources(this.sources, meal);
|
this.sources = removeMealFromShoppingListSources(this.sources, meal);
|
||||||
|
},
|
||||||
|
async addIngredient() {
|
||||||
|
const person = await data.currentUser();
|
||||||
|
if (!person) return;
|
||||||
|
|
||||||
|
this.sources = this.sources.concat([{ ingredient: { id: 0 }, person }]);
|
||||||
|
},
|
||||||
|
async deleteIngredient(ingredient) {
|
||||||
|
const person = await data.currentUser();
|
||||||
|
if (!person) return;
|
||||||
|
|
||||||
|
const isMatch = source => source.ingredient === ingredient && source.person?.id === person.id;
|
||||||
|
this.sources = this.sources.filter(source => !isMatch(source));
|
||||||
|
},
|
||||||
|
async updateIngredient(oldIngredient, newIngredient) {
|
||||||
|
const person = await data.currentUser();
|
||||||
|
if (!person) return;
|
||||||
|
|
||||||
|
this.sources = this.sources.map(source => source.ingredient === oldIngredient ? { ingredient: newIngredient, person } : source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,28 @@ export function mealToShoppingListSources(meal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeMealFromShoppingListSources(sources, meal) {
|
export function removeMealFromShoppingListSources(sources, meal) {
|
||||||
return sources.filter(source => source.meal.id !== meal.id);
|
return sources.filter(source => source.meal?.id !== meal.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function groupByProduct(sources) {
|
export function groupByProduct(sources) {
|
||||||
|
const noProductKey = "no-product";
|
||||||
|
const noProduct = { name: "No product", };
|
||||||
|
|
||||||
const grouped = {};
|
const grouped = {};
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (!grouped[source.ingredient.product.id]) {
|
const key = source.ingredient?.product?.id ?? noProductKey;
|
||||||
grouped[source.ingredient.product.id] = {
|
if (!key) {
|
||||||
product: source.ingredient.product,
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!grouped[key]) {
|
||||||
|
grouped[key] = {
|
||||||
|
product: source.ingredient.product ?? noProduct,
|
||||||
sources: [],
|
sources: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
grouped[source.ingredient.product.id].sources.push(source);
|
grouped[key].sources.push(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(grouped);
|
return Object.values(grouped);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue