Files
beer-inventory/app/components/cards/drink.card.tsx
T
kennyboy55 c08a035416
Build dev docker image / build (push) Successful in 23s
Build dev docker image / build (push) Successful in 35s
Build dev docker image / release (push) Successful in 1s
Bugfix: radio buttons, new badges, sort
2026-08-11 12:17:33 +02:00

103 lines
3.7 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 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 isNewlyAdded = drinkAddedAt ? (Date.now() - drinkAddedAt.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={isNewlyAdded ? "primary" : "success"}
className='position-absolute top-0 start-0 m-2 text-wrap'
style={{ zIndex: 2 }}
>
{isNewlyAdded ? "New" : (isRecentlyRestocked ? "Restocked" : "")}
</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;