107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
|
|
import { Link } from '@remix-run/react';
|
|
import { Badge, Card, ListGroup } from 'react-bootstrap';
|
|
import { DrinkComposite, isBeer, isDrinkWithContainers, isDrinkWithManufacturer, isDrinkWithStyle } from '~/models/types';
|
|
|
|
interface Arguments {
|
|
drink: DrinkComposite
|
|
}
|
|
|
|
function toDate(value: Date | string | null | undefined): Date | null {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
const parsed = value instanceof Date ? value : new Date(value);
|
|
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
}
|
|
|
|
function DrinkCard(arg:Arguments) {
|
|
var trimmedDescription = arg.drink.description.length > 120 ? arg.drink.description.substring(0, 120) + "..." : arg.drink.description;
|
|
var link = "/inventory/";
|
|
|
|
switch (arg.drink.type) {
|
|
case "Beer":
|
|
link += "beer/";
|
|
break;
|
|
case "Wine":
|
|
link += "wine/";
|
|
break;
|
|
case "Soda":
|
|
link += "soda/";
|
|
break;
|
|
case "Cocktail":
|
|
link += "cocktail/";
|
|
break;
|
|
}
|
|
|
|
var borderColor = "#bbb";
|
|
var style = (<></>);
|
|
if(isDrinkWithStyle(arg.drink)){
|
|
borderColor = "#" + arg.drink.style.color;
|
|
style = (<Badge bg="info">{arg.drink.style.name}</Badge>);
|
|
}
|
|
|
|
var ibu = (<></>);
|
|
if(isBeer(arg.drink)){
|
|
if(arg.drink.ibu)
|
|
ibu = (<Badge bg={arg.drink.ibu > 45 ? "warning" : "secondary"}>IBU: {arg.drink.ibu}</Badge>);
|
|
}
|
|
|
|
let inventoryNum = 0;
|
|
let latestActivity: Date | null = null;
|
|
if(isDrinkWithContainers(arg.drink)){
|
|
arg.drink.containers.forEach((container: { inventory: number; lastAdded?: Date | string | null }) => {
|
|
inventoryNum += container.inventory;
|
|
const candidate = toDate(container.lastAdded);
|
|
if (candidate && (!latestActivity || candidate.getTime() > latestActivity.getTime())) {
|
|
latestActivity = candidate;
|
|
}
|
|
});
|
|
}
|
|
|
|
const drinkAddedAt = toDate((arg.drink as { addedAt?: Date | string | null }).addedAt ?? null);
|
|
const latestTimestamp = drinkAddedAt && (!latestActivity || drinkAddedAt.getTime() > latestActivity.getTime())
|
|
? drinkAddedAt
|
|
: latestActivity;
|
|
const isNewlyAdded = latestTimestamp ? (Date.now() - latestTimestamp.getTime()) <= 30 * 24 * 60 * 60 * 1000 : false;
|
|
const isRecentlyRestocked = latestActivity ? (Date.now() - latestActivity.getTime()) <= 14 * 24 * 60 * 60 * 1000 : false;
|
|
|
|
return (
|
|
<Card key={arg.drink.slug} style={{ borderColor: `${borderColor}`, borderWidth: '2px' }} className='mb-2 h-100'>
|
|
<div style={{ position: 'relative', width: '100%', aspectRatio: '1 / 1', overflow: 'hidden' }}>
|
|
{ (isNewlyAdded || isRecentlyRestocked) ? (
|
|
<Badge
|
|
bg={isRecentlyRestocked ? "success" : "primary"}
|
|
className='position-absolute top-0 start-0 m-2 text-wrap'
|
|
style={{ zIndex: 2 }}
|
|
>
|
|
{isNewlyAdded && isRecentlyRestocked ? "New · Restocked" : isNewlyAdded ? "New" : "Recently added"}
|
|
</Badge>
|
|
) : null }
|
|
<Card.Img
|
|
variant="top"
|
|
className='image-fluid'
|
|
src={arg.drink.image ? arg.drink.image : ""}
|
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
/>
|
|
</div>
|
|
<Card.Body className='d-flex flex-column justify-content-between py-3'>
|
|
<Card.Title className='h6 mb-2'><Link to={link + arg.drink.slug.toString()} className='stretched-link text-reset text-decoration-none'>{arg.drink.name}</Link></Card.Title>
|
|
<div className='d-flex flex-wrap gap-1 align-items-center'>
|
|
<Badge bg={arg.drink.abv > 0 ? "warning" : "secondary"} className='py-1'>ABV: {arg.drink.abv}%</Badge>
|
|
{style}
|
|
{ibu}
|
|
{arg.drink.sugar ? "" : <Badge bg="success" className='py-1'>Sugar-free</Badge>}
|
|
{arg.drink.gluten ? "" : <Badge bg="success" className='py-1'>Gluten-free</Badge>}
|
|
{arg.drink.organic ? <Badge bg="success" className='py-1'>Organic</Badge> : ""}
|
|
</div>
|
|
</Card.Body>
|
|
<Card.Footer className="text-center py-2">
|
|
<Badge bg="dark" className='px-3 py-2 fs-6'>Inventory: {inventoryNum}</Badge>
|
|
</Card.Footer>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default DrinkCard; |