264 lines
9.6 KiB
TypeScript
264 lines
9.6 KiB
TypeScript
|
|
import { Manufacturer } from '@zenstackhq/runtime/models';
|
|
import { Alert, Badge, Button, Col, Container, Image, Row, Table } from 'react-bootstrap';
|
|
import { ContainerWithSection, DrinkComposite, containerTypeToString, isBeer, isDrinkWithContainersAndSection, isDrinkWithManufacturer, isDrinkWithStyle } from '~/models/types';
|
|
import { volume } from '~/utils/conversions';
|
|
import DrinkCard from './drink.card';
|
|
import { Link } from '@remix-run/react';
|
|
|
|
import { Bar, Chart } from 'react-chartjs-2';
|
|
import { ClientOnly } from 'remix-utils/client-only';
|
|
import 'chart.js/auto';
|
|
import LinkContainer from '../LinkContainer';
|
|
|
|
interface Arguments {
|
|
drink: DrinkComposite
|
|
recommendations: DrinkComposite[]
|
|
chartData: any
|
|
isAdmin: boolean
|
|
}
|
|
|
|
function DrinkPage(arg:Arguments) {
|
|
|
|
var borderColor = "#bbb";
|
|
var style = (<></>);
|
|
if(isDrinkWithStyle(arg.drink)){
|
|
borderColor = "#" + arg.drink.style.color;
|
|
style = (<Badge bg="info">{arg.drink.style.name}</Badge>);
|
|
}
|
|
|
|
var manufacturer: Manufacturer | undefined = undefined;
|
|
if(isDrinkWithManufacturer(arg.drink)){
|
|
manufacturer = arg.drink.manufacturer;
|
|
}
|
|
|
|
var glass = false;
|
|
var ibu = (<></>);
|
|
if(isBeer(arg.drink)){
|
|
glass = arg.drink.glass;
|
|
if(arg.drink.ibu)
|
|
ibu = (<Badge bg={arg.drink.ibu > 45 ? "warning" : "secondary"}>IBU: {arg.drink.ibu}</Badge>);
|
|
}
|
|
|
|
var containers : ContainerWithSection[] = [];
|
|
var totalinventory = 0;
|
|
var containerWithMultiplePortions = false;
|
|
if(isDrinkWithContainersAndSection(arg.drink)){
|
|
containers = arg.drink.containers;
|
|
containers.map((container) => {
|
|
totalinventory += container.inventory;
|
|
|
|
if((container.portions || 0) > 1 && container.inventory > 0){
|
|
containerWithMultiplePortions = true;
|
|
}
|
|
})
|
|
}
|
|
var link = (<></>);
|
|
if(arg.drink.link){
|
|
var href = arg.drink.link;
|
|
|
|
var linktitle = "Open link";
|
|
var linkcolor = "#6c757d";
|
|
|
|
if(href.includes("untappd")){
|
|
linkcolor = "#ffc20b";
|
|
linktitle = "Untappd";
|
|
}
|
|
if(href.includes("vivino")) {
|
|
linkcolor = "#ba1628";
|
|
linktitle = "Vivino";
|
|
}
|
|
|
|
link = (
|
|
<Button variant='dark' href={href} style={{backgroundColor: linkcolor}}>{linktitle}</Button>
|
|
);
|
|
|
|
}
|
|
|
|
const chartOptions = {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top' as const,
|
|
},
|
|
title: {
|
|
display: false
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'History'
|
|
}
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: 'Amount'
|
|
},
|
|
min: 0,
|
|
ticks: {
|
|
// forces step size to be 50 units
|
|
stepSize: 1
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Container>
|
|
{glass ? (
|
|
<Alert variant='info'>
|
|
There is a glass available for this beer!
|
|
</Alert>
|
|
): ""}
|
|
|
|
{arg.isAdmin ? (
|
|
<Row className='my-3'>
|
|
<Col>
|
|
<LinkContainer to={"/admin/edit/" + arg.drink.type.toLowerCase() + "/" + arg.drink.id}><Button variant='primary'>Edit</Button></LinkContainer>
|
|
<LinkContainer to={"/admin/edit/image/" + arg.drink.type.toLowerCase() + "/" + arg.drink.id}><Button className='mx-3' variant='secondary'>Image</Button></LinkContainer>
|
|
<div className='float-end'>
|
|
<LinkContainer to={"/inventory/drink/" + (arg.drink.id - 1)}><Button variant='secondary' className='mx-3' >Previous</Button></LinkContainer>
|
|
<LinkContainer to={"/inventory/drink/" + (arg.drink.id + 1)}><Button variant='secondary'>Next</Button></LinkContainer>
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
) : ""}
|
|
|
|
<div className="p-md-5 p-3 mb-4 bg-body-tertiary rounded-3 border" style={{ borderColor: `${borderColor}`, borderWidth: '2px' }} >
|
|
<Container fluid>
|
|
<Row>
|
|
<Col md={10}>
|
|
<h1 className="display-5 fw-bold">{arg.drink.name}</h1>
|
|
<Badge bg={arg.drink.abv > 0 ? "warning" : "secondary"}>ABV: {arg.drink.abv}%</Badge> {style} {ibu} {arg.drink.sugar ? "" : <Badge bg="success">Sugar-free</Badge>} {arg.drink.gluten ? "" : <Badge bg="success">Gluten-free</Badge>} {arg.drink.organic ? <Badge bg="success">Organic</Badge> : ""}
|
|
<p className="fs-4">{arg.drink.description}</p>
|
|
{link}
|
|
</Col>
|
|
{arg.drink.image ? (
|
|
<Col md={2}>
|
|
<Image className='mt-3' src={arg.drink.image} fluid rounded/>
|
|
</Col>
|
|
) : ""}
|
|
</Row>
|
|
|
|
</Container>
|
|
</div>
|
|
|
|
<Row className="align-items-md-stretch">
|
|
<Col md={6} className='mb-4'>
|
|
<div className="h-100 p-md-5 p-3 text-bg-dark rounded-3">
|
|
{arg.isAdmin ? (
|
|
<>
|
|
<LinkContainer to={{pathname:"/admin/new/container/", search:"?drink=" + arg.drink.id}}><Button variant='success' className='float-end'>Add</Button></LinkContainer>
|
|
</>
|
|
) : ""}
|
|
<h2>Inventory</h2>
|
|
{totalinventory == 0 && !arg.isAdmin ? (
|
|
"No inventory"
|
|
) : (
|
|
<Table data-bs-theme="dark" responsive>
|
|
<thead>
|
|
<tr>
|
|
<th>Section</th>
|
|
<th>Container</th>
|
|
<th>Volume</th>
|
|
<th>Amount</th>
|
|
{arg.isAdmin ? (
|
|
<>
|
|
<th>Price</th>
|
|
<th>Edit</th>
|
|
</>
|
|
) : ""}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{containers.map((container) => (
|
|
<tr key={container.barcode}>
|
|
<td>{container.section.name}</td>
|
|
<td>{containerTypeToString(container.type)}</td>
|
|
<td>{volume(container.volume)}</td>
|
|
<td>{container.inventory}</td>
|
|
{arg.isAdmin ? (
|
|
<>
|
|
<td>€{container.price}</td>
|
|
<td>
|
|
<LinkContainer to={"/admin/edit/container/" + container.id}><Button variant='primary'>Edit</Button></LinkContainer>
|
|
<LinkContainer to={"/admin/manage/manual-checkout/" + container.id}><Button variant='secondary'>Checkout</Button></LinkContainer>
|
|
</td>
|
|
</>
|
|
) : ""}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</Table>
|
|
)}
|
|
{containerWithMultiplePortions ? (
|
|
<Alert variant="warning">
|
|
This drink contains multiple portions! These should be scanned <u>only</u> when the container is empty!
|
|
</Alert>
|
|
) : ""}
|
|
</div>
|
|
</Col>
|
|
{ manufacturer ? (
|
|
<Col md={6} className='mb-4'>
|
|
<Container>
|
|
{arg.isAdmin ? (
|
|
<Row className='my-3'>
|
|
<Col>
|
|
<LinkContainer to={"/admin/edit/manufacturer/" + manufacturer.id}><Button variant='primary'>Edit</Button></LinkContainer>
|
|
<LinkContainer to={"/admin/edit/image/manufacturer/" + manufacturer.id}><Button variant='secondary' className='mx-3' >Image</Button></LinkContainer>
|
|
</Col>
|
|
</Row>
|
|
) : ""}
|
|
<Row className="p-md-5 p-3 bg-body-tertiary border rounded-3">
|
|
<Col md={10}>
|
|
<h2>{manufacturer.name} ({manufacturer.country_id})</h2>
|
|
<p>{manufacturer.description}</p>
|
|
<LinkContainer to={{pathname: "/inventory", search: "?manufacturer=" + manufacturer.id}} ><Button variant='secondary'>All drinks from {manufacturer.name}</Button></LinkContainer>
|
|
</Col>
|
|
{manufacturer.image ? (
|
|
<Col md={2}>
|
|
<Image src={manufacturer.image} className='mt-3' fluid rounded/>
|
|
</Col>
|
|
) : ""}
|
|
</Row>
|
|
</Container>
|
|
</Col>
|
|
) : ""}
|
|
</Row>
|
|
<Row className="mt-3 mb-3">
|
|
<Col col={12}>
|
|
<h3>Checkouts</h3>
|
|
<ClientOnly fallback={<Fallback />}>
|
|
{() => <Chart options={chartOptions} data={arg.chartData} type={"bar"} />}
|
|
</ClientOnly>
|
|
</Col>
|
|
</Row>
|
|
{arg.recommendations.length > 0 ? (
|
|
<>
|
|
<Row className="mt-3 mb-3">
|
|
<Col col={12}>
|
|
<h3>Maybe also try</h3>
|
|
<Row xs={2} md={3} lg={4} className="g-4">
|
|
{arg.recommendations.map((drink) => (
|
|
<Col >
|
|
<DrinkCard drink={drink}></DrinkCard>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
) : ""}
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
function Fallback() {
|
|
return <div>Generating Chart</div>;
|
|
}
|
|
|
|
|
|
export default DrinkPage; |