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 >
< a v-if = "shoppingListItemGroup.product?.link" :href="shoppingListItemGroup.product?.link" > {{ shoppingListItemGroup.product.name }} < / a >
< span v-else > {{ shoppingListItemGroup.name }} < / span >
< / strong > ,
2024-05-19 03:43:59 +00:00
< small >
2025-07-28 10:51:17 +00:00
< span v-for = "(total, index) in remainingRequiredTotals" :key="total.id" >
2024-05-19 03:43:59 +00:00
< 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 >
2024-05-19 03:43:59 +00:00
< / 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" >
2024-10-13 08:19:36 +00:00
< strong > Need : < / strong >
2025-07-28 10:51:17 +00:00
< span v-for = "(source, index) in required" :key="source.id" >
2024-10-13 08:19:36 +00:00
< 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" >
2024-10-13 08:19:36 +00:00
< 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 : 1 em ;
}
. product - image {
max - width : 5 em ;
max - height : 4 em ;
margin - right : 1 em ;
}
. product - details {
flex - grow : 1 ;
text - align : left ;
}
. header {
margin : 0 ;
}
2024-05-19 10:21:49 +00:00
. found - marker {
margin : 1 ex ;
padding - top : 0.6 ex ;
padding - bottom : 0.5 ex ;
padding - left : 1 em ;
padding - right : 1 em ;
border - radius : 25 px ;
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' ;
2024-10-13 08:19:36 +00:00
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 ,
}
} ,
2024-10-13 08:19:36 +00:00
computed : {
2025-07-28 10:51:17 +00:00
remainingRequiredTotals ( ) {
return calculateTotals ( this . shoppingListItemGroup . shoppingListItems . map ( item => item . ingredient ) ) ;
2024-10-13 08:19:36 +00:00
} ,
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 ) ;
2024-10-13 08:19:36 +00:00
} ,
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-10-13 08:19:36 +00:00
}
} ,
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 >