-
-
⏶ Hide Purchased ⏶
-
⏷ Show Purchased ⏷
+
-
-
Purchased Meals
-
+
+
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/src/components/shopping/MealSelectionList.vue b/src/components/shopping/MealSelectionList.vue
index a7649f8..34bd391 100644
--- a/src/components/shopping/MealSelectionList.vue
+++ b/src/components/shopping/MealSelectionList.vue
@@ -1,160 +1,166 @@
-
-
\ No newline at end of file
+ const opacity = 0.7
+ return {
+ background: `linear-gradient(to bottom, rgba(255, 255, 255, ${opacity}) 0%, rgba(255, 255, 255, ${opacity}) 100%), url('${imageUrl}') center/cover no-repeat`,
+ }
+ },
+ mealCheckChanged(event) {
+ const mealId = parseInt(event.target.id)
+ const meal = this.meals.find((m) => m.id === mealId)
+ if (event.target.checked) {
+ this.$emit('meal-selected', meal)
+ } else {
+ this.$emit('meal-unselected', meal)
+ }
+ },
+ isChecked(meal) {
+ for (const checkedMeal of this.checked) {
+ if (checkedMeal.id === meal.id) {
+ return true
+ }
+ }
+ return false
+ },
+ },
+}
+
diff --git a/src/components/shopping/MyShoppingPage.vue b/src/components/shopping/MyShoppingPage.vue
index f0aab4a..a162717 100644
--- a/src/components/shopping/MyShoppingPage.vue
+++ b/src/components/shopping/MyShoppingPage.vue
@@ -1,11 +1,17 @@
-
-
My Shopping List
- Full Shopping List
-
-
+
+
My Shopping List
+ Full Shopping List
+
+
-
-
+
\ No newline at end of file
+
diff --git a/src/components/shopping/PurchasedShoppingListPage.vue b/src/components/shopping/PurchasedShoppingListPage.vue
index 9447907..84d5e88 100644
--- a/src/components/shopping/PurchasedShoppingListPage.vue
+++ b/src/components/shopping/PurchasedShoppingListPage.vue
@@ -1,32 +1,30 @@
- Purchased {{ shoppingList ? ago(shoppingList.created_date) : '' }}
-
-
-
Included Meals
-
-
+ Purchased {{ shoppingList ? ago(shoppingList.created_date) : '' }}
-
+
+
Included Meals
+
+
+
+
\ No newline at end of file
+
diff --git a/src/components/shopping/ShoppingListItem.vue b/src/components/shopping/ShoppingListItem.vue
index c2e7442..88a5017 100644
--- a/src/components/shopping/ShoppingListItem.vue
+++ b/src/components/shopping/ShoppingListItem.vue
@@ -1,174 +1,214 @@
-
-
-
\ No newline at end of file
+ return calculateTotals(purchasedNotEaten.map((item) => item.ingredient))
+ },
+ required() {
+ return this.shoppingListItemGroup.shoppingListItems.filter((item) => !item.list_id)
+ },
+ purchased() {
+ return this.shoppingListItemGroup.shoppingListItems.filter((item) => item.list_id)
+ },
+ lastPurchased() {
+ const purchasedItems = this.shoppingListItemGroup.shoppingListItems.filter(
+ (item) => item.list_id
+ )
+ if (purchasedItems.length === 0) {
+ return null
+ }
+ // Find the most recently purchased item
+ return purchasedItems.reduce((latest, item) => {
+ const itemDate = item?.meal?.suggested_date || item?.created_at
+ return !latest || (itemDate && itemDate > latest) ? itemDate : latest
+ }, null)
+ },
+ },
+ methods: {
+ getFriendlyDate(date) {
+ if (!date) return ''
+
+ return ago(date)
+ },
+ formatQuantity(quantity) {
+ const log10 = Math.log10(quantity)
+ if (log10 < 0) {
+ return quantity.toPrecision(2)
+ } else if (log10 < 1) {
+ return quantity.toFixed(1)
+ } else {
+ return quantity.toFixed(0)
+ }
+ },
+ },
+}
+
diff --git a/src/components/shopping/shopping.js b/src/components/shopping/shopping.js
index f55731e..a469766 100644
--- a/src/components/shopping/shopping.js
+++ b/src/components/shopping/shopping.js
@@ -1,45 +1,42 @@
export function groupsToItems(groups) {
- return groups.map(group => group.shoppingListItems).flat();
+ return groups.map((group) => group.shoppingListItems).flat()
}
export function uniqueMeals(shoppingListItems) {
- const mealsWithDuplicates = shoppingListItems
- .map(item => item.meal)
- .filter(m => m);
+ const mealsWithDuplicates = shoppingListItems.map((item) => item.meal).filter((m) => m)
- const mealsLookup = mealsWithDuplicates.reduce(((acc, meal) => { acc[meal.id] ??= meal; return acc; }), {});
+ const mealsLookup = mealsWithDuplicates.reduce((acc, meal) => {
+ acc[meal.id] ??= meal
+ return acc
+ }, {})
- return Object.values(mealsLookup);
+ return Object.values(mealsLookup)
}
export function itemsToGroups(shoppingListItems) {
- const ingredients_by_product_id = {};
- const ingredients_by_name = {};
- for (const item of shoppingListItems) {
- if (item.ingredient.product) {
- let group = ingredients_by_product_id[item.ingredient.product.id];
- if (!group) {
- group = ingredients_by_product_id[item.ingredient.product.id] = {
- product: item.ingredient.product,
- shoppingListItems: []
- };
- }
- group.shoppingListItems.push(item);
+ const ingredients_by_product_id = {}
+ const ingredients_by_name = {}
+ for (const item of shoppingListItems) {
+ if (item.ingredient.product) {
+ let group = ingredients_by_product_id[item.ingredient.product.id]
+ if (!group) {
+ group = ingredients_by_product_id[item.ingredient.product.id] = {
+ product: item.ingredient.product,
+ shoppingListItems: [],
}
- else {
- let group = ingredients_by_name[item.ingredient.name];
- if (!group) {
- group = ingredients_by_name[item.ingredient.name] = {
- name: item.ingredient.name,
- shoppingListItems: []
- };
- }
- group.shoppingListItems.push(item);
+ }
+ group.shoppingListItems.push(item)
+ } else {
+ let group = ingredients_by_name[item.ingredient.name]
+ if (!group) {
+ group = ingredients_by_name[item.ingredient.name] = {
+ name: item.ingredient.name,
+ shoppingListItems: [],
}
+ }
+ group.shoppingListItems.push(item)
}
+ }
- return [
- ...Object.values(ingredients_by_product_id),
- ...Object.values(ingredients_by_name)
- ]
-}
\ No newline at end of file
+ return [...Object.values(ingredients_by_product_id), ...Object.values(ingredients_by_name)]
+}
diff --git a/src/data.js b/src/data.js
index 322bf2a..6b91d88 100644
--- a/src/data.js
+++ b/src/data.js
@@ -1,332 +1,390 @@
-const BASE_URL = window.location.href.replace(/^(https?:\/\/[^/]+).*/, "$1/api");
+const BASE_URL = window.location.href.replace(/^(https?:\/\/[^/]+).*/, '$1/api')
const datesToFix = {
- Meal: { fields: [ "suggested_date", "purchase_date", "consumed_date" ] },
- CurrentShoppingList: { dependants: l => ({ ShoppingListItem: [l.outstanding_items, l.requested_meals, l.purchased_items], Meal: Object.values(l.meals_lookup), Recipe: Object.values(l.recipes_lookup), Ingredient: Object.values(l.ingredients_lookup), ShoppingList: Object.values(l.shopping_list_lookup) }) },
- PurchasedShoppingList: { dependants: l => ({ ShoppingListItem: l.items, Meal: Object.values(l.meals_lookup), Recipe: Object.values(l.recipes_lookup), Ingredient: Object.values(l.ingredients_lookup), ShoppingList: l.list }) },
- ShoppingList: { fields: [ "created_date" ], dependants: l => ({ ShoppingListItem: l.items }) },
- ShoppingListItem: { fields: [ "created_date" ], dependants: r => ({ Meal: r.meal, Recipe: r.recipe, Ingredient: r.ingredient, ShoppingList: r.list }) },
- Recipe: { fields: [ "date_created", "date_hidden" ], },
-};
+ Meal: { fields: ['suggested_date', 'purchase_date', 'consumed_date'] },
+ CurrentShoppingList: {
+ dependants: (l) => ({
+ ShoppingListItem: [l.outstanding_items, l.requested_meals, l.purchased_items],
+ Meal: Object.values(l.meals_lookup),
+ Recipe: Object.values(l.recipes_lookup),
+ Ingredient: Object.values(l.ingredients_lookup),
+ ShoppingList: Object.values(l.shopping_list_lookup),
+ }),
+ },
+ PurchasedShoppingList: {
+ dependants: (l) => ({
+ ShoppingListItem: l.items,
+ Meal: Object.values(l.meals_lookup),
+ Recipe: Object.values(l.recipes_lookup),
+ Ingredient: Object.values(l.ingredients_lookup),
+ ShoppingList: l.list,
+ }),
+ },
+ ShoppingList: { fields: ['created_date'], dependants: (l) => ({ ShoppingListItem: l.items }) },
+ ShoppingListItem: {
+ fields: ['created_date'],
+ dependants: (r) => ({
+ Meal: r.meal,
+ Recipe: r.recipe,
+ Ingredient: r.ingredient,
+ ShoppingList: r.list,
+ }),
+ },
+ Recipe: { fields: ['date_created', 'date_hidden'] },
+}
const fixDates = (obj, type) => {
- if (!obj) return;
+ if (!obj) return
- if (Array.isArray(obj)) {
- for (const item of obj) {
- fixDates(item, type);
- }
+ if (Array.isArray(obj)) {
+ for (const item of obj) {
+ fixDates(item, type)
}
+ }
- const toFix = datesToFix[type];
- if (!toFix) return;
+ const toFix = datesToFix[type]
+ if (!toFix) return
- if (toFix.fields) {
- for (const field of toFix.fields) {
- if (obj[field]) {
- obj[field] = new Date(obj[field]);
- }
- }
+ if (toFix.fields) {
+ for (const field of toFix.fields) {
+ if (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);
- }
+ 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 setPurchasedShoppingListReferences = (purchasedShoppingList) => {
- if (!purchasedShoppingList) return;
- const { ingredients_lookup, meals_lookup, recipes_lookup, } = purchasedShoppingList;
- const shopping_list_lookup = { [purchasedShoppingList.list.id]: purchasedShoppingList.list };
- setShoppingListItemReferences(purchasedShoppingList.list.items, ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup);
+ if (!purchasedShoppingList) return
+ const { ingredients_lookup, meals_lookup, recipes_lookup } = purchasedShoppingList
+ const shopping_list_lookup = { [purchasedShoppingList.list.id]: purchasedShoppingList.list }
+ setShoppingListItemReferences(
+ purchasedShoppingList.list.items,
+ ingredients_lookup,
+ meals_lookup,
+ recipes_lookup,
+ shopping_list_lookup
+ )
}
const setCurrentShoppingListReferences = (currentShoppingList) => {
- if (!currentShoppingList) return;
- const { ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup } = currentShoppingList;
- const allShoppingListItems = [...currentShoppingList.outstanding_items, ...currentShoppingList.requested_meals, ...currentShoppingList.purchased_items];
+ if (!currentShoppingList) return
+ const { ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup } =
+ currentShoppingList
+ const allShoppingListItems = [
+ ...currentShoppingList.outstanding_items,
+ ...currentShoppingList.requested_meals,
+ ...currentShoppingList.purchased_items,
+ ]
- setShoppingListItemReferences(allShoppingListItems, ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup);
+ setShoppingListItemReferences(
+ allShoppingListItems,
+ ingredients_lookup,
+ meals_lookup,
+ recipes_lookup,
+ shopping_list_lookup
+ )
}
-const setShoppingListItemReferences = (shoppingListItems, ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup) => {
- if (!shoppingListItems) return;
+const setShoppingListItemReferences = (
+ shoppingListItems,
+ ingredients_lookup,
+ meals_lookup,
+ recipes_lookup,
+ shopping_list_lookup
+) => {
+ if (!shoppingListItems) return
- for (const item of shoppingListItems) {
- if (item.ingredient_id) {
- item.ingredient = ingredients_lookup[item.ingredient_id];
- }
- if (item.meal_id) {
- item.meal = meals_lookup[item.meal_id];
- }
- if (item.list_id) {
- item.list = shopping_list_lookup[item.list_id];
- }
- if (item.recipe_id) {
- item.recipe = recipes_lookup[item.recipe_id];
- }
+ for (const item of shoppingListItems) {
+ if (item.ingredient_id) {
+ item.ingredient = ingredients_lookup[item.ingredient_id]
}
+ if (item.meal_id) {
+ item.meal = meals_lookup[item.meal_id]
+ }
+ if (item.list_id) {
+ item.list = shopping_list_lookup[item.list_id]
+ }
+ if (item.recipe_id) {
+ item.recipe = recipes_lookup[item.recipe_id]
+ }
+ }
}
-let user = null;
+let user = null
export default {
- async markMealConsumed(meal_id) {
- const response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal_id)}/consumed`, {
- method: "POST",
- credentials: "include",
- });
+ async markMealConsumed(meal_id) {
+ const response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal_id)}/consumed`, {
+ method: 'POST',
+ credentials: 'include',
+ })
- const meal = await response.json();
- fixDates(meal, "Meal");
+ const meal = await response.json()
+ fixDates(meal, 'Meal')
- return meal;
- },
- async getUpcomingMeals(from, to) {
- const response = await fetch(BASE_URL + "/meals/upcoming?from=" + from.toISOString() + "&to=" + to.toISOString());
- const meals = await response.json();
- fixDates(meals, "Meal");
+ return meal
+ },
+ async getUpcomingMeals(from, to) {
+ const response = await fetch(
+ BASE_URL + '/meals/upcoming?from=' + from.toISOString() + '&to=' + to.toISOString()
+ )
+ const meals = await response.json()
+ fixDates(meals, 'Meal')
- return meals.sort((a, b) => a.suggested_date - b.suggested_date);
- },
- async getMeal(id) {
- var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`);
- const meal = await response.json();
- fixDates(meal, "Meal");
+ return meals.sort((a, b) => a.suggested_date - b.suggested_date)
+ },
+ async getMeal(id) {
+ var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`)
+ const meal = await response.json()
+ fixDates(meal, 'Meal')
- return meal;
- },
- async deleteMeal(id) {
- var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`, {
- method: "DELETE",
- });
+ return meal
+ },
+ async deleteMeal(id) {
+ var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`, {
+ method: 'DELETE',
+ })
- return await response.json();
- },
- async searchRecipes(query) {
- const response = await fetch(BASE_URL + "/recipes?q=" + encodeURIComponent(query));
- const recipes = await response.json();
- fixDates(recipes, "Recipe");
+ return await response.json()
+ },
+ async searchRecipes(query) {
+ const response = await fetch(BASE_URL + '/recipes?q=' + encodeURIComponent(query))
+ const recipes = await response.json()
+ fixDates(recipes, 'Recipe')
- return recipes;
- },
- async parseRecipe(url) {
- const response = await fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`, { credentials: "include" });
- const recipe = await response.json();
- fixDates(recipe, "Recipe");
+ return recipes
+ },
+ async parseRecipe(url) {
+ const response = await fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`, {
+ credentials: 'include',
+ })
+ const recipe = await response.json()
+ fixDates(recipe, 'Recipe')
- return recipe;
- },
- async getRecipe(id) {
- const response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`);
- const recipe = await response.json();
- fixDates(recipe, "Recipe");
+ return recipe
+ },
+ async getRecipe(id) {
+ const response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`)
+ const recipe = await response.json()
+ fixDates(recipe, 'Recipe')
- return recipe;
- },
- async parseProduct(ingredient, url) {
- const body = {
- url, tags: [ingredient.name, ingredient.line],
- };
-
- const response = await fetch(BASE_URL + "/products", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify(body),
- });
-
- return await response.json();
- },
- async parseIngredients(lines) {
- const params = lines.map(line => "ingredients=" + encodeURIComponent(line)).join("&");
- const response = await fetch(BASE_URL + "/recipes/ingredients/parse?" + params);
- return await response.json();
- },
- async saveRecipe(recipe) {
- const response = await fetch(BASE_URL + "/recipes", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify(recipe),
- });
-
- const saved = await response.json();
- fixDates(saved, "Recipe");
-
- return saved;
- },
- async saveMeal(meal) {
- let response = null;
- if (meal.id >= 0) {
- response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal.id)}`, {
- method: "PUT",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify(meal),
- });
- } else {
- response = await fetch(BASE_URL + "/meals", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify(meal),
- });
- }
-
- const saved = await response.json();
- fixDates(saved, "Meal");
-
- return saved;
- },
- async currentUser() {
- if (user) {
- return user;
- }
-
- var cookie = decodeURIComponent(document.cookie).split(";").find(cookie => cookie.trimStart().startsWith("user_id="));
- if (cookie) {
- const response = await fetch(BASE_URL + "/auth/refresh", {
- method: "POST",
- credentials: "include",
- });
-
- if (response.ok) {
- user = await response.json();
- }
- }
-
- return user;
- },
- async login(username) {
- const response = await fetch(BASE_URL + "/auth/login", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ username }),
- });
-
- if (response.ok) {
- user = await response.json();
- }
-
- return user;
- },
- async deleteRecipe(id) {
- var response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`, {
- method: "DELETE",
- credentials: "include",
- });
-
- const recipe = await response.json();
- fixDates(recipe, "Recipe");
- return recipe;
- },
- async searchPerson(name) {
- const response = await fetch(BASE_URL + "/persons?q=" + encodeURIComponent(name));
- return await response.json();
- },
- async getMyShoppingList() {
- const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", { credentials: "include" });
- const ingredients = await response.json();
- fixDates(ingredients, 'Ingredient');
-
- return ingredients;
- },
- async saveMyShoppingList(list) {
- const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify(list),
- });
-
- const ingredients = await response.json();
- fixDates(ingredients, 'Ingredient');
-
- return ingredients;
- },
- async getShoppingList(id) {
- const response = await fetch(BASE_URL + `/shopping/${encodeURIComponent(id)}`);
-
- const purchasedShoppingList = await response.json();
- fixDates(purchasedShoppingList, "PurchasedShoppingList");
- setPurchasedShoppingListReferences(purchasedShoppingList);
-
- return purchasedShoppingList.list;
- },
- async getCurrentShoppingList() {
- const response = await fetch(BASE_URL + "/shopping/current");
-
- const lst = await response.json();
- fixDates(lst, "CurrentShoppingList");
- setCurrentShoppingListReferences(lst);
-
- return lst;
- },
- async purchaseShoppingList(completed_requests) {
- const response = await fetch(BASE_URL + "/shopping/", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ items: completed_requests }),
- });
-
- const purchasedShoppingList = await response.json();
- fixDates(purchasedShoppingList, "PurchasedShoppingList");
- setPurchasedShoppingListReferences(purchasedShoppingList);
-
- return purchasedShoppingList.list;
- },
- async requestMeal(meal_id) {
- const response = await fetch(BASE_URL + "/shopping/current/meals/me", {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ meal_id }),
- });
-
- const requests = await response.json();
- fixDates(requests, "ShoppingListItem");
-
- return requests;
- },
- async unrequestMeal(meal_id) {
- const response = await fetch(BASE_URL + `/shopping/current/meals/${encodeURIComponent(meal_id)}`, {
- method: "DELETE",
- credentials: "include",
- });
-
- if (!response.ok) {
- throw new Error("Failed to unrequest meal");
- }
- },
- async getPersonsInHome() {
- const response = await fetch(BASE_URL + "/persons");
- return await response.json();
+ return recipe
+ },
+ async parseProduct(ingredient, url) {
+ const body = {
+ url,
+ tags: [ingredient.name, ingredient.line],
}
-}
\ No newline at end of file
+
+ const response = await fetch(BASE_URL + '/products', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ })
+
+ return await response.json()
+ },
+ async parseIngredients(lines) {
+ const params = lines.map((line) => 'ingredients=' + encodeURIComponent(line)).join('&')
+ const response = await fetch(BASE_URL + '/recipes/ingredients/parse?' + params)
+ return await response.json()
+ },
+ async saveRecipe(recipe) {
+ const response = await fetch(BASE_URL + '/recipes', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(recipe),
+ })
+
+ const saved = await response.json()
+ fixDates(saved, 'Recipe')
+
+ return saved
+ },
+ async saveMeal(meal) {
+ let response = null
+ if (meal.id >= 0) {
+ response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal.id)}`, {
+ method: 'PUT',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(meal),
+ })
+ } else {
+ response = await fetch(BASE_URL + '/meals', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(meal),
+ })
+ }
+
+ const saved = await response.json()
+ fixDates(saved, 'Meal')
+
+ return saved
+ },
+ async currentUser() {
+ if (user) {
+ return user
+ }
+
+ var cookie = decodeURIComponent(document.cookie)
+ .split(';')
+ .find((cookie) => cookie.trimStart().startsWith('user_id='))
+ if (cookie) {
+ const response = await fetch(BASE_URL + '/auth/refresh', {
+ method: 'POST',
+ credentials: 'include',
+ })
+
+ if (response.ok) {
+ user = await response.json()
+ }
+ }
+
+ return user
+ },
+ async login(username) {
+ const response = await fetch(BASE_URL + '/auth/login', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ username }),
+ })
+
+ if (response.ok) {
+ user = await response.json()
+ }
+
+ return user
+ },
+ async deleteRecipe(id) {
+ var response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`, {
+ method: 'DELETE',
+ credentials: 'include',
+ })
+
+ const recipe = await response.json()
+ fixDates(recipe, 'Recipe')
+ return recipe
+ },
+ async searchPerson(name) {
+ const response = await fetch(BASE_URL + '/persons?q=' + encodeURIComponent(name))
+ return await response.json()
+ },
+ async getMyShoppingList() {
+ const response = await fetch(BASE_URL + '/shopping/current/me/ingredients', {
+ credentials: 'include',
+ })
+ const ingredients = await response.json()
+ fixDates(ingredients, 'Ingredient')
+
+ return ingredients
+ },
+ async saveMyShoppingList(list) {
+ const response = await fetch(BASE_URL + '/shopping/current/me/ingredients', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(list),
+ })
+
+ const ingredients = await response.json()
+ fixDates(ingredients, 'Ingredient')
+
+ return ingredients
+ },
+ async getShoppingList(id) {
+ const response = await fetch(BASE_URL + `/shopping/${encodeURIComponent(id)}`)
+
+ const purchasedShoppingList = await response.json()
+ fixDates(purchasedShoppingList, 'PurchasedShoppingList')
+ setPurchasedShoppingListReferences(purchasedShoppingList)
+
+ return purchasedShoppingList.list
+ },
+ async getCurrentShoppingList() {
+ const response = await fetch(BASE_URL + '/shopping/current')
+
+ const lst = await response.json()
+ fixDates(lst, 'CurrentShoppingList')
+ setCurrentShoppingListReferences(lst)
+
+ return lst
+ },
+ async purchaseShoppingList(completed_requests) {
+ const response = await fetch(BASE_URL + '/shopping/', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ items: completed_requests }),
+ })
+
+ const purchasedShoppingList = await response.json()
+ fixDates(purchasedShoppingList, 'PurchasedShoppingList')
+ setPurchasedShoppingListReferences(purchasedShoppingList)
+
+ return purchasedShoppingList.list
+ },
+ async requestMeal(meal_id) {
+ const response = await fetch(BASE_URL + '/shopping/current/meals/me', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ meal_id }),
+ })
+
+ const requests = await response.json()
+ fixDates(requests, 'ShoppingListItem')
+
+ return requests
+ },
+ async unrequestMeal(meal_id) {
+ const response = await fetch(
+ BASE_URL + `/shopping/current/meals/${encodeURIComponent(meal_id)}`,
+ {
+ method: 'DELETE',
+ credentials: 'include',
+ }
+ )
+
+ if (!response.ok) {
+ throw new Error('Failed to unrequest meal')
+ }
+ },
+ async getPersonsInHome() {
+ const response = await fetch(BASE_URL + '/persons')
+ return await response.json()
+ },
+}
diff --git a/src/dateformats.js b/src/dateformats.js
index 5425fde..8ae1891 100644
--- a/src/dateformats.js
+++ b/src/dateformats.js
@@ -1,25 +1,25 @@
function plural(num, unit) {
- num = Math.floor(num);
- return num + " " + unit + (num === 1 ? "" : "s");
+ num = Math.floor(num)
+ return num + ' ' + unit + (num === 1 ? '' : 's')
}
export function ago(date) {
- const now = new Date();
- const diff = now - date;
- if (diff < 1000) {
- return "just now";
- }
- if (diff < 60 * 1000) {
- return plural(diff / 1000, "second") + " ago";
- }
- if (diff < 60 * 60 * 1000) {
- return plural(diff / (60 * 1000), "minute") + " ago";
- }
- if (diff < 24 * 60 * 60 * 1000) {
- return plural(diff / (60 * 60 * 1000), "hour") + " ago";
- }
- if (diff < 7 * 24 * 60 * 60 * 1000) {
- return plural(diff / (24 * 60 * 60 * 1000), "day") + " ago";
- }
- return date.toLocaleDateString();
-}
\ No newline at end of file
+ const now = new Date()
+ const diff = now - date
+ if (diff < 1000) {
+ return 'just now'
+ }
+ if (diff < 60 * 1000) {
+ return plural(diff / 1000, 'second') + ' ago'
+ }
+ if (diff < 60 * 60 * 1000) {
+ return plural(diff / (60 * 1000), 'minute') + ' ago'
+ }
+ if (diff < 24 * 60 * 60 * 1000) {
+ return plural(diff / (60 * 60 * 1000), 'hour') + ' ago'
+ }
+ if (diff < 7 * 24 * 60 * 60 * 1000) {
+ return plural(diff / (24 * 60 * 60 * 1000), 'day') + ' ago'
+ }
+ return date.toLocaleDateString()
+}
diff --git a/src/main.js b/src/main.js
index 6d74738..4b58570 100644
--- a/src/main.js
+++ b/src/main.js
@@ -8,4 +8,4 @@ const router = createAppRouter(() => currentUser())
const app = createApp(App)
app.use(router)
-app.mount('#app')
\ No newline at end of file
+app.mount('#app')
diff --git a/src/router/index.js b/src/router/index.js
index 1d2a9d3..2fcaf01 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -5,7 +5,8 @@ const LoginPage = () => import('@/components/LoginPage.vue')
const RecipesPage = () => import('@/components/recipes/RecipesPage.vue')
const MealPlanPage = () => import('@/components/meals/MealPlanPage.vue')
const MyShoppingPage = () => import('@/components/shopping/MyShoppingPage.vue')
-const PurchasedShoppingListPage = () => import('@/components/shopping/PurchasedShoppingListPage.vue')
+const PurchasedShoppingListPage = () =>
+ import('@/components/shopping/PurchasedShoppingListPage.vue')
const CurrentShoppingListPage = () => import('@/components/shopping/CurrentShoppingListPage.vue')
const EditMealPage = () => import('@/components/meals/EditMealPage.vue')
const EditRecipePage = () => import('@/components/recipes/EditRecipePage.vue')
@@ -15,14 +16,47 @@ export function createAppRouter(getCurrentUser) {
{ path: '/', redirect: { name: 'mealplan' } },
{ path: '/login', name: 'login', component: LoginPage },
{ path: '/mealplan', name: 'mealplan', component: MealPlanPage, meta: { requiresAuth: true } },
- { path: '/shopping', name: 'shopping', component: MyShoppingPage, meta: { requiresAuth: true } },
- { path: '/shopping/current', name: 'shopping-current', component: CurrentShoppingListPage, meta: { requiresAuth: true } },
- { path: '/shopping/:id', name: 'shopping-list', component: PurchasedShoppingListPage, props: true, meta: { requiresAuth: true } },
+ {
+ path: '/shopping',
+ name: 'shopping',
+ component: MyShoppingPage,
+ meta: { requiresAuth: true },
+ },
+ {
+ path: '/shopping/current',
+ name: 'shopping-current',
+ component: CurrentShoppingListPage,
+ meta: { requiresAuth: true },
+ },
+ {
+ path: '/shopping/:id',
+ name: 'shopping-list',
+ component: PurchasedShoppingListPage,
+ props: true,
+ meta: { requiresAuth: true },
+ },
{ path: '/recipes', name: 'recipes', component: RecipesPage, meta: { requiresAuth: true } },
- { path: '/recipes/add', name: 'recipe-add', component: EditRecipePage, meta: { requiresAuth: true } },
- { path: '/recipes/:id', name: 'recipe-edit', component: EditRecipePage, props: true, meta: { requiresAuth: true } },
+ {
+ path: '/recipes/add',
+ name: 'recipe-add',
+ component: EditRecipePage,
+ meta: { requiresAuth: true },
+ },
+ {
+ path: '/recipes/:id',
+ name: 'recipe-edit',
+ component: EditRecipePage,
+ props: true,
+ meta: { requiresAuth: true },
+ },
{ path: '/meals/add', name: 'meal-add', component: EditMealPage, meta: { requiresAuth: true } },
- { path: '/meals/:id', name: 'meal-edit', component: EditMealPage, props: true, meta: { requiresAuth: true } },
+ {
+ path: '/meals/:id',
+ name: 'meal-edit',
+ component: EditMealPage,
+ props: true,
+ meta: { requiresAuth: true },
+ },
]
const router = createRouter({
@@ -36,7 +70,9 @@ export function createAppRouter(getCurrentUser) {
try {
const user = await getCurrentUser()
if (user) return true
- } catch (_) { /* ignore */ }
+ } catch (_) {
+ /* ignore */
+ }
return { name: 'login', query: { redirect: to.fullPath } }
})
diff --git a/src/units.js b/src/units.js
index 1d34d76..59f4a4a 100644
--- a/src/units.js
+++ b/src/units.js
@@ -1,142 +1,142 @@
export const equivalentUnits = {
- 'kg': {
- 'kgs': 1,
- 'kilograms': 1,
- 'kilogram': 1,
+ kg: {
+ kgs: 1,
+ kilograms: 1,
+ kilogram: 1,
- 'g': 1000,
- 'gram': 1000,
- 'grams': 1000,
+ g: 1000,
+ gram: 1000,
+ grams: 1000,
- 'lb': 2.20462,
- 'lbs': 2.20462,
- 'pound': 2.20462,
- 'pounds': 2.20462,
- },
- 'litres': {
- 'l': 1,
- 'liter': 1,
- 'litre': 1,
+ lb: 2.20462,
+ lbs: 2.20462,
+ pound: 2.20462,
+ pounds: 2.20462,
+ },
+ litres: {
+ l: 1,
+ liter: 1,
+ litre: 1,
- 'ml': 1000,
- 'milliliters': 1000,
- 'milliliter': 1000,
+ ml: 1000,
+ milliliters: 1000,
+ milliliter: 1000,
- 'fl oz': 33.814,
- 'fluid ounce': 33.814,
- 'fluid ounces': 33.814,
+ 'fl oz': 33.814,
+ 'fluid ounce': 33.814,
+ 'fluid ounces': 33.814,
- 'cup': 4.22675,
- 'cups': 4.22675,
+ cup: 4.22675,
+ cups: 4.22675,
- 'tbsp': 67.628,
- 'tablespoon': 67.628,
- 'tablespoons': 67.628,
+ tbsp: 67.628,
+ tablespoon: 67.628,
+ tablespoons: 67.628,
- 'tsp': 202.884,
- 'teaspoon': 202.884,
- 'teaspoons': 202.884,
+ tsp: 202.884,
+ teaspoon: 202.884,
+ teaspoons: 202.884,
- 'pt': 2.11338,
- 'pint': 2.11338,
- 'pints': 2.11338,
+ pt: 2.11338,
+ pint: 2.11338,
+ pints: 2.11338,
- 'qt': 1.05669,
- 'quart': 1.05669,
- 'quarts': 1.05669,
+ qt: 1.05669,
+ quart: 1.05669,
+ quarts: 1.05669,
- 'gal': 0.264172,
- 'gallon': 0.264172,
- 'gallons': 0.264172,
+ gal: 0.264172,
+ gallon: 0.264172,
+ gallons: 0.264172,
- 'oz': 35.1951,
- 'ounce': 35.1951,
- },
- 'items': {
- 'item': 1,
- 'items': 1,
- 'pcs': 1,
- 'piece': 1,
- 'pieces': 1,
- 'florets': 8, // Broccoli
- 'head': 1, // Broccoli
- 'heads': 1, // Broccoli
- 'slice': 10, // Bread
- 'slices': 10, // Bread
- 'loaf': 1, // Bread
- 'loaves': 1, // Bread
- 'cloves': 8, // Garlic
- 'bulb': 1, // Garlic
- 'bulbs': 1, // Garlic
- 'stalk': 1, // Celery
- 'stalks': 1, // Celery
- 'bunch': 1, // Cilantro
- 'bunches': 1, // Cilantro
- 'sprig': 1, // Cilantro
- 'sprigs': 1, // Cilantro
- 'cans': 1, // Canned goods
- 'can': 1, // Canned goods
- 'pack': 1, // Packaged goods
- 'packs': 1, // Packaged goods
- 'package': 1, // Packaged goods
- 'packages': 1, // Packaged goods
- 'container': 1, // Packaged goods
- 'containers': 1, // Packaged goods
- },
+ oz: 35.1951,
+ ounce: 35.1951,
+ },
+ items: {
+ item: 1,
+ items: 1,
+ pcs: 1,
+ piece: 1,
+ pieces: 1,
+ florets: 8, // Broccoli
+ head: 1, // Broccoli
+ heads: 1, // Broccoli
+ slice: 10, // Bread
+ slices: 10, // Bread
+ loaf: 1, // Bread
+ loaves: 1, // Bread
+ cloves: 8, // Garlic
+ bulb: 1, // Garlic
+ bulbs: 1, // Garlic
+ stalk: 1, // Celery
+ stalks: 1, // Celery
+ bunch: 1, // Cilantro
+ bunches: 1, // Cilantro
+ sprig: 1, // Cilantro
+ sprigs: 1, // Cilantro
+ cans: 1, // Canned goods
+ can: 1, // Canned goods
+ pack: 1, // Packaged goods
+ packs: 1, // Packaged goods
+ package: 1, // Packaged goods
+ packages: 1, // Packaged goods
+ container: 1, // Packaged goods
+ containers: 1, // Packaged goods
+ },
}
function getBaseUnit(unit) {
- for (const unitType in equivalentUnits) {
- if (unit in equivalentUnits[unitType]) {
- return unitType;
- }
+ for (const unitType in equivalentUnits) {
+ if (unit in equivalentUnits[unitType]) {
+ return unitType
}
+ }
- return null;
+ return null
}
export function getConversionFactor(unit) {
- if (unit in equivalentUnits) {
- return { unit, factor: 1 };
- }
+ if (unit in equivalentUnits) {
+ return { unit, factor: 1 }
+ }
- const unitLower = unit.toLowerCase();
- if (unitLower in equivalentUnits) {
- return { unit: unitLower, factor: 1 };
- }
-
- const baseUnit = getBaseUnit(unit);
- if (baseUnit) {
- return {
- unit: baseUnit,
- factor: equivalentUnits[baseUnit][unit],
- };
- }
+ const unitLower = unit.toLowerCase()
+ if (unitLower in equivalentUnits) {
+ return { unit: unitLower, factor: 1 }
+ }
- const baseUnitLower = getBaseUnit(unitLower);
- if (baseUnitLower) {
- return {
- unit: baseUnitLower,
- factor: equivalentUnits[baseUnitLower][unitLower],
- };
+ const baseUnit = getBaseUnit(unit)
+ if (baseUnit) {
+ return {
+ unit: baseUnit,
+ factor: equivalentUnits[baseUnit][unit],
}
+ }
- return null;
+ const baseUnitLower = getBaseUnit(unitLower)
+ if (baseUnitLower) {
+ return {
+ unit: baseUnitLower,
+ factor: equivalentUnits[baseUnitLower][unitLower],
+ }
+ }
+
+ return null
}
export function calculateTotals(quantityList) {
- const totals = {};
- for (const quantity of quantityList) {
- const baseUnit = getConversionFactor(quantity.unit) ?? { unit: quantity.unit, factor: 1 };
- const factor = baseUnit.factor;
- const unit = baseUnit.unit;
+ const totals = {}
+ for (const quantity of quantityList) {
+ const baseUnit = getConversionFactor(quantity.unit) ?? { unit: quantity.unit, factor: 1 }
+ const factor = baseUnit.factor
+ const unit = baseUnit.unit
- if (!totals[unit]) {
- totals[unit] = 0;
- }
-
- totals[unit] += quantity.quantity / factor;
+ if (!totals[unit]) {
+ totals[unit] = 0
}
- return Object.keys(totals).map(unit => ({ unit, quantity: totals[unit] }));
-}
\ No newline at end of file
+ totals[unit] += quantity.quantity / factor
+ }
+
+ return Object.keys(totals).map((unit) => ({ unit, quantity: totals[unit] }))
+}
diff --git a/vue.config.js b/vue.config.js
index 910e297..523a634 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,4 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
- transpileDependencies: true
+ transpileDependencies: true,
})