munch-ease-frontend/src/components/shopping/ShoppingListItem.vue

174 lines
6.4 KiB
Vue
Raw Normal View History

2024-05-11 06:03:29 +00:00
<template>
<!-- Show a card of the product with the total, taking up the available space and have an expanding section to view sources -->
<div class="shopping-list-item">
2025-07-28 10:51:17 +00:00
<img :src="`${ shoppingListItemGroup.product?.img_small ?? require('@/assets/missing-product.svg') }`" class="product-image" />
2024-05-11 06:03:29 +00:00
<div class="product-details">
<h3 class="header">
2025-07-28 10:51:17 +00:00
<strong>
2025-07-28 23:07:44 +00:00
<a v-if="shoppingListItemGroup.product?.link" :href="shoppingListItemGroup.product?.link">{{ shoppingListItemGroup.product?.name }}</a>
2025-07-28 10:51:17 +00:00
<span v-else>{{ shoppingListItemGroup.name }}</span>
</strong>,
<small>
2025-07-28 10:51:17 +00:00
<span v-for="(total, index) in remainingRequiredTotals" :key="total.id">
<span v-if="index">, </span>
2024-05-19 11:57:28 +00:00
<span>{{ formatQuantity(total.quantity) }}&nbsp;{{ total.unit }}</span>
2024-05-19 10:21:49 +00:00
</span>
2025-07-28 10:51:17 +00:00
<span class="found-marker partial" v-if="purchased.length > 0"> {{ getFriendlyDate(lastPurchased) }}</span>
</small>
2024-05-11 06:03:29 +00:00
</h3>
2025-07-28 10:51:17 +00:00
<p class="sources" v-if="required.length > 0">
<strong>Need: </strong>
2025-07-28 10:51:17 +00:00
<span v-for="(source, index) in required" :key="source.id">
<span v-if="index">, and </span>
<span v-if="source.person">
{{ formatQuantity(source.ingredient.quantity) }}&nbsp;{{ source.ingredient.unit }} for {{ source.person.name }}
</span>
<span v-else-if="source.recipe">
{{ formatQuantity(source.ingredient.quantity) }}&nbsp;{{ source.ingredient.unit }}
in <router-link :to="`/recipes/${ source.recipe.id }/`">{{ source.recipe.name }}</router-link>
for <router-link :to="`/meals/${ source.meal.id }/`">{{ source.meal.suggested_date.toLocaleDateString("en-AU", { weekday: 'long', month: 'long', day: 'numeric' }) }}</router-link>
</span>
<span v-else-if="source.meal">
{{ source.ingredient.line }} for <router-link :to="`/meals/${ source.meal.id }/`">{{ source.meal.suggested_date.toLocaleDateString("en-AU", { weekday: 'long', month: 'long', day: 'numeric' }) }}</router-link>
</span>
</span>
</p>
2025-07-28 10:51:17 +00:00
<p v-if="purchased.length > 0">
<strong>Already found or purchased: </strong>
2025-07-28 10:51:17 +00:00
<span v-for="(source, index) in purchased" :key="source.id">
2024-05-11 06:03:29 +00:00
<span v-if="index">, and </span>
2024-05-12 03:32:18 +00:00
<span v-if="source.person">
2024-05-19 11:57:28 +00:00
{{ formatQuantity(source.ingredient.quantity) }}&nbsp;{{ source.ingredient.unit }} for {{ source.person.name }}
2024-05-11 06:03:29 +00:00
</span>
2024-05-12 03:32:18 +00:00
<span v-else-if="source.recipe">
2024-05-19 11:57:28 +00:00
{{ formatQuantity(source.ingredient.quantity) }}&nbsp;{{ source.ingredient.unit }}
2024-05-11 06:03:29 +00:00
in <router-link :to="`/recipes/${ source.recipe.id }/`">{{ source.recipe.name }}</router-link>
2024-05-25 02:09:27 +00:00
for <router-link :to="`/meals/${ source.meal.id }/`">{{ source.meal.suggested_date.toLocaleDateString("en-AU", { weekday: 'long', month: 'long', day: 'numeric' }) }}</router-link>
2024-05-11 06:03:29 +00:00
</span>
2024-05-12 03:32:18 +00:00
<span v-else-if="source.meal">
2024-05-25 02:09:27 +00:00
{{ source.ingredient.line }} for <router-link :to="`/meals/${ source.meal.id }/`">{{ source.meal.suggested_date.toLocaleDateString("en-AU", { weekday: 'long', month: 'long', day: 'numeric' }) }}</router-link>
2024-05-11 06:03:29 +00:00
</span>
</span>
</p>
</div>
</div>
</template>
<style scoped>
/* Show the product image to the left, then the product name and size to the right */
.shopping-list-item {
2024-05-19 10:21:49 +00:00
position: relative;
2024-05-11 06:03:29 +00:00
display: flex;
justify-content: space-between;
align-items: center;
padding: 1em;
}
.product-image {
max-width: 5em;
max-height: 4em;
margin-right: 1em;
}
.product-details {
flex-grow: 1;
text-align: left;
}
.header {
margin: 0;
}
2024-05-19 10:21:49 +00:00
.found-marker {
margin: 1ex;
padding-top: 0.6ex;
padding-bottom: 0.5ex;
padding-left: 1em;
padding-right: 1em;
border-radius: 25px;
text-align: center;
white-space: nowrap;
font-size: smaller;
color: white;
font-weight: bold;
z-index: 1000;
}
.found-marker.found {
background-color: green;
}
.found-marker.partial {
background-color: darkgoldenrod;
}
2024-05-11 06:03:29 +00:00
</style>
<script>
2024-10-15 08:40:06 +00:00
import { ago } from '@/dateformats.js';
import { calculateTotals } from '@/units.js';
2024-05-11 06:03:29 +00:00
export default {
name: 'ShoppingListItem',
2025-07-28 10:51:17 +00:00
props: ['shoppingListItemGroup' ], // { product: { ... }, OR name: 'string', shoppingListItems: { person, ingredient, list_id?, meal? }} where list_id is null if not yet purchased
2024-05-11 06:03:29 +00:00
data() {
return {
expanded: false,
}
},
computed: {
2025-07-28 10:51:17 +00:00
remainingRequiredTotals() {
return calculateTotals(this.shoppingListItemGroup.shoppingListItems.map(item => item.ingredient));
},
2025-07-28 10:51:17 +00:00
expectedExistingTotals() {
const purchasedNotEaten = this.shoppingListItemGroup.shoppingListItems.filter(item => item.list_id && !(item?.meal?.consumed_date));
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() {
2025-07-28 10:51:17 +00:00
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);
}
},
2024-05-19 10:21:49 +00:00
methods: {
getFriendlyDate(date) {
if (!date)
return '';
2024-10-15 08:40:06 +00:00
return ago(date);
2024-05-19 11:57:28 +00:00
},
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);
}
2024-05-19 10:21:49 +00:00
}
}
2024-05-11 06:03:29 +00:00
}
</script>