Compare commits

...

10 commits

13 changed files with 343 additions and 88 deletions

View file

@ -1,19 +1,77 @@
<template> <template>
<div class="login"> <div class="login">
<h1>Login</h1> <h1>Login Page</h1>
<form> <ul class="button-group">
<div class="form-group"> <li v-for="person in persons" :key="person.id">
<label for="username">Username</label> <button type="button" class="btn btn-primary" @click="login(person)">
<input v-model="username" type="text" class="form-control" id="username" placeholder="Username"> {{ person.name }}
</div> </button>
</li>
<button type="button" class="btn btn-primary" @click="login">Login</button> </ul>
</form>
</div> </div>
</template> </template>
<style scoped>
/* Remove the default list styling */
ul {
list-style-type: none;
padding: 0;
}
/* Center the buttons in the middle of the page, and let them wrap */
.button-group {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
/* Display each button as a large round circle */
button {
width: 100px;
height: 100px;
border-radius: 50%;
margin: 10px;
font-size: 1.5em;
/* Center the text in the middle of the button */
display: flex;
justify-content: center;
align-items: center;
/* Add a shadow to make the buttons look like they are floating */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
/* Some nice flat nuetral shades for the user buttons */
li:nth-child(1) > button {
background-color: #3939ff;
color: white;
}
li:nth-child(2) > button {
background-color: #156a14;
color: white;
}
li:nth-child(3) > button {
background-color: #325293;
color: white;
}
li:nth-child(4) > button {
background-color: #9a1f1f;
color: white;
}
</style>
<script> <script>
import data from '@/data'; import data from '@/data';
@ -23,17 +81,20 @@ export default {
redirect: { redirect: {
type: String, type: String,
default: '/' default: '/'
} },
}, },
data() { data() {
return { return {
username: '', persons: [],
} }
}, },
async beforeMount() {
this.persons = await data.getPersonsInHome();
},
methods: { methods: {
async login() { async login(selectedPerson) {
const person = await data.login(this.username); const person = await data.login(selectedPerson.name);
if (person?.id) { if (person?.id >= 0) {
this.$router.push(this.redirect); this.$router.push(this.redirect);
return; return;
} }
@ -44,6 +105,3 @@ export default {
} }
</script> </script>
<style>
</style>

View file

@ -31,7 +31,7 @@
<h2>Sides & Additional Ingredients</h2> <h2>Sides & Additional Ingredients</h2>
<editable-ingredients-panel :ingredients="meal.extra_ingredients" @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" /> <editable-ingredients-panel :ingredients="meal.extra_ingredients" @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" />
</div> </div>
<button @click="saveMeal">Save</button> <button @click="saveMeal" v-if="!meal.purchase_date">Save</button>
</div> </div>
</template> </template>
@ -43,13 +43,19 @@ import DatePicker from './DatePicker.vue';
import EditableIngredientsPanel from '../ingredients/EditableIngredientsPanel.vue'; import EditableIngredientsPanel from '../ingredients/EditableIngredientsPanel.vue';
import PersonList from './PersonList.vue'; import PersonList from './PersonList.vue';
function addPersonIfNotExists(list, person) {
if (!list.find(p => p.id === person.id)) {
list.push(person);
}
}
export default { export default {
props: ['id'], props: ['id'],
components: { RecipeSearchBox, DatePicker, RecipeCard, EditableIngredientsPanel, PersonList }, components: { RecipeSearchBox, DatePicker, RecipeCard, EditableIngredientsPanel, PersonList },
data() { data() {
return { return {
meal: { meal: {
id: 0, id: -1,
meal_date: new Date(), meal_date: new Date(),
recipes: [], recipes: [],
extra_ingredients: [], extra_ingredients: [],
@ -60,7 +66,7 @@ export default {
}; };
}, },
async beforeMount() { async beforeMount() {
if (this.id) { if (this.id >= 0) {
this.meal = await data.getMeal(this.id); this.meal = await data.getMeal(this.id);
} }
}, },
@ -70,11 +76,11 @@ export default {
recipe = await data.getRecipe(recipe.id); recipe = await data.getRecipe(recipe.id);
if (recipe.created_by) { if (recipe.created_by) {
this.meal.chefs.push(recipe.created_by); addPersonIfNotExists(this.meal.chefs, recipe.created_by);
this.meal.consumers.push(recipe.created_by); addPersonIfNotExists(this.meal.consumers, recipe.created_by);
if (this.meal.cleanup.length === 0) { if (this.meal.cleanup.length === 0) {
this.meal.cleanup.push(recipe.created_by); addPersonIfNotExists(this.meal.cleanup, recipe.created_by);
} }
} }
@ -99,11 +105,11 @@ export default {
this.meal[list] = this.meal[list].filter(p => p.id !== person.id); this.meal[list] = this.meal[list].filter(p => p.id !== person.id);
}, },
addPerson(list, person) { addPerson(list, person) {
this.meal[list].push(person); addPersonIfNotExists(this.meal[list], person);
}, },
async saveMeal() { async saveMeal() {
const meal = await data.saveMeal(this.meal); const meal = await data.saveMeal(this.meal);
if (meal?.id) { if (meal?.id >= 0) {
this.$router.push(`/meals/${meal.id}`); this.$router.push(`/meals/${meal.id}`);
return; return;
} }

View file

@ -39,6 +39,9 @@
</span> </span>
<span v-if="!meal.consumers.length">somebody?</span> <span v-if="!meal.consumers.length">somebody?</span>
</p> </p>
<p v-if="meal.purchase_date">
Purchased {{ formatDate(meal.purchase_date) }}
</p>
</div> </div>
</template> </template>
@ -67,6 +70,25 @@ export default {
default: default:
return ', '; return ', ';
} }
},
formatDate(datetime) {
// If its less than a week, use "2 minutes ago" style
const diff = new Date() - datetime;
if (diff < 1000 * 60 * 60 * 24 * 7) {
const minutes = Math.floor(diff / 1000 / 60);
if (minutes < 60) {
return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
}
const hours = Math.floor(minutes / 60);
if (hours < 24) {
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
}
const days = Math.floor(hours / 24);
return `${days} day${days === 1 ? '' : 's'} ago`;
}
// Otherwise, use a date format with the day of week prepended
return `${datetime.toLocaleDateString('en-au', { weekday: 'long' })} ${datetime.toLocaleDateString('en-au', { month: 'numeric', day: 'numeric' })}`;
} }
} }
} }

View file

@ -148,12 +148,12 @@ export default {
this.searchResults = results.filter(p => !idSet.has(p.id)); this.searchResults = results.filter(p => !idSet.has(p.id));
}, },
addPerson(person) { addPerson(person) {
if (!person?.id && this.searchResults.length > 0) if (!person?.id >= 0 && this.searchResults.length > 0)
{ {
person = this.searchResults[0]; person = this.searchResults[0];
} }
if (person?.id && !this.people.find(p => p.id === person.id)) if (person?.id >= 0 && !this.people.find(p => p.id === person.id))
{ {
this.$emit('add-person', person); this.$emit('add-person', person);
} }

View file

@ -99,7 +99,7 @@ export default {
this.refreshRecipe(); this.refreshRecipe();
}, },
async refreshRecipe() { async refreshRecipe() {
if (this.id) { if (this.id >= 0) {
this.recipe = await data.getRecipe(this.id); this.recipe = await data.getRecipe(this.id);
this.link = this.recipe.link; this.link = this.recipe.link;
return; return;
@ -120,7 +120,7 @@ export default {
}, },
async saveRecipe() { async saveRecipe() {
const recipe = await data.saveRecipe(this.recipe); const recipe = await data.saveRecipe(this.recipe);
if (recipe?.id) { if (recipe?.id >= 0) {
this.$router.push(`/recipes/${recipe.id}`); this.$router.push(`/recipes/${recipe.id}`);
return; return;
} }
@ -129,8 +129,9 @@ export default {
}, },
async createFromScratch() { async createFromScratch() {
this.recipe = { this.recipe = {
id: 0, id: -1,
name: 'My new recipe', name: 'My new recipe',
created_by_id: -1,
link: '', link: '',
ingredients: [], ingredients: [],
image_urls: [] image_urls: []

View file

@ -1,15 +1,23 @@
<template> <template>
<h3>Full shopping list</h3> <h3>Full shopping list</h3>
<input type="checkbox" id="show-found" v-model="stockTaking" />
<label for="show-found">Show Stocktaking</label>
<h4>Included Meals</h4> <h4>Included Meals</h4>
<meal-selection-list @meal-selected="mealSelected" @meal-unselected="mealUnselected" :checked="includedMeals" :meals="availableMeals" /> <meal-selection-list @meal-selected="mealSelected" @meal-unselected="mealUnselected" :checked="includedMeals" :meals="availableMeals" />
<ul class="full-shopping-list"> <ul class="full-shopping-list">
<li v-for="item in listByProduct" :key="item.id" > <li v-for="item in productsToShow" :key="item.id">
<shopping-list-item :product="item.product" :sources="item.sources" :totals="item.totals" :fully-found="item.fullyFound" :found-date="item.foundDate" /> <shopping-list-item :product="item.product" :sources="item.sources" :totals="item.totals" :fully-found="item.fullyFound" :found-date="item.foundDate" />
<span v-if="stockTaking">
<button v-if="item.fullyFound" @click="markNotFound(item)">Mark Not Found</button> <button v-if="item.fullyFound" @click="markNotFound(item)">Mark Not Found</button>
<button v-else @click="markFound(item)">Mark Found</button> <button v-else @click="markFound(item)">Mark Found</button>
</span>
</li> </li>
</ul> </ul>
<button @click="markPurchased">Purchased, archive this list!</button>
</template> </template>
<style scoped> <style scoped>
@ -39,9 +47,6 @@ import ShoppingListItem from './ShoppingListItem.vue'
export default { export default {
name: 'FullShoppingListPage', name: 'FullShoppingListPage',
components: { MealSelectionList, ShoppingListItem }, components: { MealSelectionList, ShoppingListItem },
props: {
id: [String, Number]
},
data() { data() {
const from = new Date(); const from = new Date();
from.setTime(0); from.setTime(0);
@ -49,17 +54,19 @@ export default {
const to = new Date(); const to = new Date();
to.setDate(to.getDate() + 7); to.setDate(to.getDate() + 7);
return { from, to, availableMeals: [], shoppingList: null, includedMeals: [], listByProduct: [] } return { from, to, availableMeals: [], shoppingList: null, includedMeals: [], listByProduct: [], stockTaking: false, productsToShow: []}
}, },
async beforeMount() { async beforeMount() {
this.availableMeals = await data.getMeals(this.from, this.to); const allMeals = await data.getMeals(this.from, this.to);
this.availableMeals = allMeals.filter(m => !m.purchase_date);
this.shoppingList = await data.getCurrentShoppingList(); this.shoppingList = await data.getCurrentShoppingList();
}, },
watch: { watch: {
shoppingList: { shoppingList: {
handler: 'updateLists', handler: 'updateLists',
deep: true deep: true
} },
stockTaking: 'updateLists'
}, },
methods: { methods: {
async updateLists() { async updateLists() {
@ -68,6 +75,7 @@ export default {
this.includedMeals = this.shoppingList.requests.map(r => r.meal).filter(m => m); this.includedMeals = this.shoppingList.requests.map(r => r.meal).filter(m => m);
this.listByProduct = toProductsModel(this.shoppingList); this.listByProduct = toProductsModel(this.shoppingList);
this.productsToShow = this.stockTaking ? this.listByProduct : this.listByProduct.filter(p => !p.fullyFound);
}, },
async mealSelected(meal) { async mealSelected(meal) {
const request = await data.requestMeal(meal.id); const request = await data.requestMeal(meal.id);
@ -87,6 +95,10 @@ export default {
async markNotFound(item) { async markNotFound(item) {
const deletedRequests = await data.markNotFound(item.product.id); const deletedRequests = await data.markNotFound(item.product.id);
this.shoppingList.results = this.shoppingList.results.filter(r => !deletedRequests.some(deleted => deleted.id === r.id)); this.shoppingList.results = this.shoppingList.results.filter(r => !deletedRequests.some(deleted => deleted.id === r.id));
},
async markPurchased() {
const result = await data.markCurrentShoppingListPurchased();
this.$router.push({ name: 'PurchasedShoppingListPage', params: { id: result.id } });
} }
} }
} }

View file

@ -4,7 +4,7 @@
<li v-for="meal in meals" :key="meal.id"> <li v-for="meal in meals" :key="meal.id">
<!-- Have a checkbox and card for each meal, show the image and name --> <!-- Have a checkbox and card for each meal, show the image and name -->
<!-- Emit meal-selected event on checked and meal-unselected on unchecked --> <!-- Emit meal-selected event on checked and meal-unselected on unchecked -->
<input type="checkbox" :id="meal.id" :checked="isChecked(meal)" @change="mealCheckChanged" /> <input type="checkbox" :id="meal.id" :checked="isChecked(meal)" @change="mealCheckChanged" :disabled="!!meal.purchase_date" />
<label :for="meal.id" :style="getImageStyling(meal)"> <label :for="meal.id" :style="getImageStyling(meal)">
{{ formatDate(meal.meal_date) }} {{ formatDate(meal.meal_date) }}
</label> </label>
@ -45,15 +45,18 @@ label {
margin: 0; margin: 0;
font-weight: normal; font-weight: normal;
cursor: pointer; cursor: pointer;
color: #777; color: #3d5447;
} }
input[type="checkbox"]:checked + label { input[type="checkbox"]:checked + label {
border: 3px solid #3d5447; border: 3px solid #3d5447;
color: #3d5447;
text-shadow: #ccc 0 0 0.1em; text-shadow: #ccc 0 0 0.1em;
} }
input[type="checkbox"]:disabled + label {
cursor: not-allowed;
}
/* Show the image as the background image of the card */ /* Show the image as the background image of the card */
.meal-image { .meal-image {
width: 100%; width: 100%;

View file

@ -63,7 +63,7 @@ export default {
this.ingredients = requests.map(r => r.ingredient); this.ingredients = requests.map(r => r.ingredient);
}, },
addIngredient() { addIngredient() {
this.ingredients = [{ id: 0 }, ...this.ingredients]; this.ingredients = [{ id: -1 }, ...this.ingredients];
}, },
deleteIngredient(ingredient) { deleteIngredient(ingredient) {
this.ingredients = this.ingredients.filter(i => i !== ingredient); this.ingredients = this.ingredients.filter(i => i !== ingredient);

View file

@ -0,0 +1,59 @@
<template>
<h3>Purchased {{ shoppingList?.purchased_date?.toLocaleDateString({ year: 'numeric', month: '2-digit', day: '2-digit' }) || 'Loading...' }}</h3>
<h4>Included Meals</h4>
<meal-selection-list :checked="includedMeals" :meals="includedMeals" />
<ul class="full-shopping-list">
<li v-for="item in productsToShow" :key="item.id">
<shopping-list-item :product="item.product" :sources="item.sources" :totals="item.totals" :fully-found="item.fullyFound" :found-date="item.foundDate" />
</li>
</ul>
</template>
<style scoped>
.full-shopping-list li {
list-style-type: none;
border: 1px solid #ccc;
border-radius: 0.5em;
margin-bottom: 1em;
}
.full-shopping-list {
padding: 0;
}
</style>
<script>
import data from '@/data.js'
import { toProductsModel } from './shopping.js'
import MealSelectionList from './MealSelectionList.vue'
import ShoppingListItem from './ShoppingListItem.vue'
export default {
name: 'FullShoppingListPage',
components: { MealSelectionList, ShoppingListItem },
props: {
id: [String, Number]
},
data() {
return {
shoppingList: null,
includedMeals: [],
listByProduct: [],
}
},
async beforeMount() {
this.shoppingList = await data.getShoppingList(this.id);
this.includedMeals = this.shoppingList.requests.map(r => r.meal).filter(m => m);
this.listByProduct = toProductsModel(this.shoppingList);
this.productsToShow = this.stockTaking ? this.listByProduct : this.listByProduct.filter(p => !p.fullyFound);
},
}
</script>

View file

@ -77,7 +77,6 @@
color: white; color: white;
font-weight: bold; font-weight: bold;
z-index: 1000; z-index: 1000;
opacity: 0.6;
} }
.found-marker.found { .found-marker.found {

View file

@ -46,7 +46,7 @@ export function groupingToIngredients(grouping) {
const product = grouping.product; const product = grouping.product;
const totals = []; const totals = [];
for (const total of grouping.totals) { for (const total of grouping.totals) {
totals.push({ id: 0, line: `${total.quantity} ${total.unit} of ${product.name}`, preparation: '', name: product.name, product, ...total }); totals.push({ id: -1, line: `${total.quantity} ${total.unit} of ${product.name}`, preparation: '', name: product.name, product, ...total });
} }
return totals; return totals;
} }
@ -103,7 +103,20 @@ function groupBy(groups, keyFn) {
return grouped; return grouped;
} }
function convertToBaseUnits(total) {
const baseUnits = {};
for (const unit in total) {
const conversion = units.getConversionFactor(unit) ?? { unit, factor: 1 };
baseUnits[conversion.unit] = (baseUnits[conversion.unit] ?? 0) + total[unit] / conversion.factor;
}
return baseUnits;
}
function getRemainingIngredients(requiredTotals, foundTotals) { function getRemainingIngredients(requiredTotals, foundTotals) {
requiredTotals = convertToBaseUnits(requiredTotals);
foundTotals = convertToBaseUnits(foundTotals);
const remaining = {}; const remaining = {};
for (const unit in requiredTotals) { for (const unit in requiredTotals) {
const required = requiredTotals[unit]; const required = requiredTotals[unit];

View file

@ -1,46 +1,89 @@
const BASE_URL = window.location.href.replace(/^(http:\/\/[^/:]+).*/, "$1:8000") const BASE_URL = window.location.href.replace(/^(http:\/\/[^/:]+).*/, "$1:8000")
function fixDates(obj, ...fields) { const datesToFix = {
for (const field of fields) { Meal: { fields: [ "meal_date", "purchase_date" ] },
ShoppingList: { fields: [ "created_date", "purchased_date" ], dependants: l => ({ ShoppingListResult: l.results, ShoppingListRequest: l.requests }) },
ShoppingListResult: { fields: [ "found_date", "created_date" ], },
ShoppingListRequest: { fields: [ "created_date" ], dependants: r => ({ Meal: r.meal }) },
Recipe: { fields: [ "date_created", "date_hidden" ], },
};
const fixDates = (obj, type) => {
if (!obj) return;
if (Array.isArray(obj)) {
for (const item of obj) {
fixDates(item, type);
}
}
const toFix = datesToFix[type];
if (!toFix) return;
for (const field of toFix.fields) {
if (obj[field]) { if (obj[field]) {
obj[field] = new Date(obj[field]); obj[field] = new Date(obj[field]);
} }
} }
if (toFix.dependants) {
for (const [key, value] of Object.entries(toFix.dependants(obj))) {
if (Array.isArray(value)) {
for (const item of value) {
fixDates(item, key);
}
}
else {
fixDates(value, key);
}
}
}
} }
const fixMealDates = meals => meals.forEach(meal => fixDates(meal, "meal_date"));
const fixResultDates = results => results.forEach(result => fixDates(result, "found_date"));
let user = null; let user = null;
export default { export default {
async getMeals(from, to) { async getMeals(from, to) {
const response = await fetch(BASE_URL + "/meals?from=" + from.toISOString() + "&to=" + to.toISOString()); const response = await fetch(BASE_URL + "/meals?from=" + from.toISOString() + "&to=" + to.toISOString());
const meals = await response.json(); const meals = await response.json();
fixMealDates(meals); fixDates(meals, "Meal");
return meals.sort((a, b) => a.meal_date - b.meal_date); return meals.sort((a, b) => a.meal_date - b.meal_date);
}, },
async getMeal(id) { async getMeal(id) {
var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`); var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`);
return await response.json(); const meal = await response.json();
fixDates(meal, "Meal");
return meal;
}, },
async deleteMeal(id) { async deleteMeal(id) {
var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`, { var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`, {
method: "DELETE", method: "DELETE",
}); });
return await response.json(); return await response.json();
}, },
async searchRecipes(query) { async searchRecipes(query) {
const response = await fetch(BASE_URL + "/recipes?q=" + encodeURIComponent(query)); const response = await fetch(BASE_URL + "/recipes?q=" + encodeURIComponent(query));
return await response.json(); const recipes = await response.json();
fixDates(recipes, "Recipe");
return recipes;
}, },
async parseRecipe(url) { async parseRecipe(url) {
const response = await fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`, { credentials: "include" }); const response = await fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`, { credentials: "include" });
return await response.json(); const recipe = await response.json();
fixDates(recipe, "Recipe");
return recipe;
}, },
async getRecipe(id) { async getRecipe(id) {
const response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`); const response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`);
return await response.json(); const recipe = await response.json();
fixDates(recipe, "Recipe");
return recipe;
}, },
async parseProduct(ingredient, url) { async parseProduct(ingredient, url) {
const body = { const body = {
@ -73,11 +116,15 @@ export default {
body: JSON.stringify(recipe), body: JSON.stringify(recipe),
}); });
return await response.json(); const saved = await response.json();
fixDates(saved, "Recipe");
return saved;
}, },
async saveMeal(meal) { async saveMeal(meal) {
if (meal.id) { let response = null;
const response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal.id)}`, { if (meal.id >= 0) {
response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal.id)}`, {
method: "PUT", method: "PUT",
credentials: "include", credentials: "include",
headers: { headers: {
@ -85,10 +132,8 @@ export default {
}, },
body: JSON.stringify(meal), body: JSON.stringify(meal),
}); });
return await response.json(); } else {
} response = await fetch(BASE_URL + "/meals", {
const response = await fetch(BASE_URL + "/meals", {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
headers: { headers: {
@ -96,8 +141,12 @@ export default {
}, },
body: JSON.stringify(meal), body: JSON.stringify(meal),
}); });
}
return await response.json(); const saved = await response.json();
fixDates(saved, "Meal");
return saved;
}, },
async currentUser() { async currentUser() {
if (user) { if (user) {
@ -139,7 +188,10 @@ export default {
method: "DELETE", method: "DELETE",
credentials: "include", credentials: "include",
}); });
return await response.json();
const recipe = await response.json();
fixDates(recipe, "Recipe");
return recipe;
}, },
async searchPerson(name) { async searchPerson(name) {
const response = await fetch(BASE_URL + "/persons?q=" + encodeURIComponent(name)); const response = await fetch(BASE_URL + "/persons?q=" + encodeURIComponent(name));
@ -147,7 +199,10 @@ export default {
}, },
async getMyShoppingList() { async getMyShoppingList() {
const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", { credentials: "include" }); const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", { credentials: "include" });
return await response.json(); const requests = await response.json();
fixDates(requests, "ShoppingListRequest");
return requests;
}, },
async saveMyShoppingList(list) { async saveMyShoppingList(list) {
const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", { const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", {
@ -159,18 +214,22 @@ export default {
body: JSON.stringify(list), body: JSON.stringify(list),
}); });
return await response.json(); const requests = await response.json();
fixDates(requests, "ShoppingListRequest");
return requests;
}, },
async getShoppingList(id) { async getShoppingList(id) {
const response = await fetch(BASE_URL + `/shopping/${encodeURIComponent(id)}`); const response = await fetch(BASE_URL + `/shopping/${encodeURIComponent(id)}`);
return await response.json();
const lst = await response.json();
fixDates(lst, "ShoppingList");
return lst;
}, },
async getCurrentShoppingList() { async getCurrentShoppingList() {
const response = await fetch(BASE_URL + "/shopping/current"); const response = await fetch(BASE_URL + "/shopping/current");
const lst = await response.json(); const lst = await response.json();
fixMealDates(lst.requests.map(request => request.meal).filter(meal => meal)); fixDates(lst, "ShoppingList");
fixResultDates(lst.results);
fixDates(lst, "created_date");
return lst; return lst;
}, },
@ -184,9 +243,10 @@ export default {
body: JSON.stringify({ meal_id }), body: JSON.stringify({ meal_id }),
}); });
const result = await response.json(); const requests = await response.json();
fixMealDates([result.meal]); fixDates(requests, "ShoppingListRequest");
return result;
return requests;
}, },
async unrequestMeal(meal_id) { async unrequestMeal(meal_id) {
const response = await fetch(BASE_URL + `/shopping/current/meals/${encodeURIComponent(meal_id)}`, { const response = await fetch(BASE_URL + `/shopping/current/meals/${encodeURIComponent(meal_id)}`, {
@ -194,7 +254,8 @@ export default {
credentials: "include", credentials: "include",
}); });
return await response.json(); const requests = await response.json();
fixDates(requests, "ShoppingListRequest");
}, },
async markFound(ingredients) { async markFound(ingredients) {
const response = await fetch(BASE_URL + "/shopping/current/found", { const response = await fetch(BASE_URL + "/shopping/current/found", {
@ -207,8 +268,9 @@ export default {
}); });
const results = await response.json(); const results = await response.json();
fixResultDates(results.created); fixDates(results.created, "ShoppingListResult");
fixResultDates(results.removed); fixDates(results.removed, "ShoppingListResult");
return results; return results;
}, },
async markNotFound(product_id) { async markNotFound(product_id) {
@ -217,8 +279,24 @@ export default {
credentials: "include", credentials: "include",
}); });
const results = await response.json(); const removedResults = await response.json();
fixResultDates(results); fixDates(removedResults, "ShoppingListResult");
return results;
return removedResults;
},
async markCurrentShoppingListPurchased() {
const response = await fetch(BASE_URL + "/shopping/current/purchased", {
method: "POST",
credentials: "include",
});
const lst = await response.json();
fixDates(lst, "ShoppingList");
return lst;
},
async getPersonsInHome() {
const response = await fetch(BASE_URL + "/persons");
return await response.json();
} }
} }

View file

@ -7,19 +7,23 @@ import LoginPage from './components/LoginPage.vue'
import RecipesPage from './components/recipes/RecipesPage.vue' import RecipesPage from './components/recipes/RecipesPage.vue'
import MealPlanPage from './components/meals/MealPlanPage.vue' import MealPlanPage from './components/meals/MealPlanPage.vue'
import MyShoppingPage from './components/shopping/MyShoppingPage.vue' import MyShoppingPage from './components/shopping/MyShoppingPage.vue'
import FullShoppingListPage from './components/shopping/FullShoppingListPage.vue' import PurchasedShoppingListPage from './components/shopping/PurchasedShoppingListPage.vue'
import CurrentShoppingListPage from './components/shopping/CurrentShoppingListPage.vue'
import EditMealPage from './components/meals/EditMealPage.vue' import EditMealPage from './components/meals/EditMealPage.vue'
import EditRecipePage from './components/recipes/EditRecipePage.vue' import EditRecipePage from './components/recipes/EditRecipePage.vue'
// 2. Define some routes // 2. Define some routes
// Each route should map to a component. // Each route should map to a component.
// We'll talk about nested routes later. // We'll talk about nested routes later.
const routes = [ const routes = [
{ path: '/', component: MealPlanPage }, // Redirect index to mealplan
{ path: '/', redirect: '/mealplan' },
{ path: '/mealplan', component: MealPlanPage }, { path: '/mealplan', component: MealPlanPage },
{ path: '/login', component: LoginPage }, { path: '/login', component: LoginPage },
{ path: '/shopping', component: MyShoppingPage }, { path: '/shopping', component: MyShoppingPage },
{ path: '/shopping/:id', component: FullShoppingListPage, props : true }, { path: '/shopping/current', component: CurrentShoppingListPage },
{ path: '/shopping/:id', component: PurchasedShoppingListPage, props : true },
{ path: '/recipes', component: RecipesPage }, { path: '/recipes', component: RecipesPage },
{ path: '/recipes/add', component: EditRecipePage }, { path: '/recipes/add', component: EditRecipePage },
{ path: '/recipes/:id', component: EditRecipePage, props: true }, { path: '/recipes/:id', component: EditRecipePage, props: true },