Complete management interface and small changes

This commit is contained in:
2024-05-26 12:13:37 +02:00
parent 2a58847b4b
commit 8e86c95939
56 changed files with 2801 additions and 55 deletions
+33 -15
View File
@@ -1,7 +1,8 @@
import { Manufacturer } from '@zenstackhq/runtime/models';
import { Col, Container, Image, Row, Table } from 'react-bootstrap';
import { ContainerWithSection, DrinkComposite, isDrinkWithContainersAndSection, isDrinkWithManufacturer, isDrinkWithStyle } from '~/models/types';
import { Alert, Badge, Col, Container, Image, Row, Table } from 'react-bootstrap';
import { ContainerWithSection, DrinkComposite, containerTypeToString, isBeer, isDrinkWithContainersAndSection, isDrinkWithManufacturer, isDrinkWithStyle } from '~/models/types';
import { volume } from '~/utils/conversions';
interface Arguments {
drink: DrinkComposite
@@ -10,8 +11,10 @@ interface Arguments {
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;
@@ -19,40 +22,48 @@ function DrinkPage(arg:Arguments) {
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){
containerWithMultiplePortions = true;
}
})
}
return (
<Container>
{glass ? (
<Alert variant='info'>
There is a glass available for this beer!
</Alert>
): ""}
<div className="p-5 mb-4 bg-body-tertiary rounded-3 border" style={{ borderColor: `${borderColor}`, borderWidth: '2px' }} >
<Container fluid>
<Row>
<Col md={11}>
<h1 className="display-5 fw-bold">{arg.drink.name}</h1>
ABV: {arg.drink.abv}% {arg.drink.gluten ? "" : " | Gluten-free"} {arg.drink.organic ? " | Organic" : ""}
<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="col-md-8 fs-4">{arg.drink.description}</p>
</Col>
<Col md={1}>
<Image src={arg.drink.image ?? undefined} fluid rounded/>
</Col>
</Row>
<Row>
<Col>
</Col>
<Col>
</Col>
<Col>
</Col>
</Row>
</Container>
</div>
@@ -69,6 +80,7 @@ function DrinkPage(arg:Arguments) {
<tr>
<th>Section</th>
<th>Container</th>
<th>Volume</th>
<th>Amount</th>
</tr>
</thead>
@@ -76,13 +88,19 @@ function DrinkPage(arg:Arguments) {
{containers.map((container) => (
<tr key={container.barcode}>
<td>{container.section.name}</td>
<td>{container.type}</td>
<td>{containerTypeToString(container.type)}</td>
<td>{volume(container.volume)}</td>
<td>{container.inventory}</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 ? (
+1 -1
View File
@@ -90,7 +90,7 @@ function DrinkFilter(arg : Arguments) {
</Form.Group>
<Form.Group controlId="drink-modifiers" className='mb-2'>
<Form.Label>Drink</Form.Label>
{ ["Gluten-free", "Organic"].map(key => (
{ ["Sugar-free", "Gluten-free", "Organic"].map(key => (
<Form.Check
id={"drinkmod-" + key}
type="checkbox"
+3
View File
@@ -12,6 +12,9 @@ export async function findDrinksFromSearch(searchParams: URLSearchParams) : Prom
let whereBase = {delegate_aux_drink: {AND: <any>[]}};
if(searchParams.has("sugar-free")){
whereBase.delegate_aux_drink.AND.push({NOT: {sugar: true}});
}
if(searchParams.has("gluten-free")){
whereBase.delegate_aux_drink.AND.push({NOT: {gluten: true}});
}
+3
View File
@@ -5,6 +5,9 @@ import { DrinkType, Manufacturer } from "@prisma/client";
export async function findIdFromSlug(slug:string | undefined) : Promise<number | undefined> {
if(slug){
const id = Number(slug);
if(id) return id;
const drink = await db.drink.findUnique({where: {slug: slug}, select: {id: true}});
return drink?.id;
}
+8 -3
View File
@@ -14,7 +14,7 @@ export type DrinkWithManufacturer = Drink & { manufacturer: Manufacturer}
export type BeerWithStyle = Beer & { style : BeerStyle }
export type WineWithStyle = Wine & { style : WineStyle }
export type DrinkComposite = Drink | BeerWithStyle | WineWithStyle | Soda | Cocktail | DrinkWithManufacturer | DrinkWithContainers
export type DrinkComposite = Drink | Beer | Wine | BeerWithStyle | WineWithStyle | Soda | Cocktail | DrinkWithManufacturer | DrinkWithContainers
export function isDrinkWithContainers(drink:DrinkComposite) : drink is DrinkWithContainers{
return ((drink as { containers: Container[]}).containers != undefined) || ((drink as { containers: ContainerWithSection[]}).containers != undefined);
@@ -43,10 +43,15 @@ export function isDrinkWithStyle(drink:DrinkComposite) : drink is WineWithStyle
return beerstyle || winestyle;
}
export function isBeer(drink:DrinkComposite) : drink is Beer {
const beer = (drink as Beer).ibu != undefined;
return beer;
}
export function containerTypeToString(type: ContainerType){
switch(type){
case "BeerBottle":
return "Beer";
return "Beer bottle";
case "Can":
return "Can";
case "Carton":
@@ -54,6 +59,6 @@ export function containerTypeToString(type: ContainerType){
case "PlasticBottle":
return "PET";
case "WineBottle":
return "Wine";
return "Wine bottle";
}
}
+201
View File
@@ -0,0 +1,201 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, InputGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const slug = String(form.get("slug"));
const manufacturer = Number(form.get("manufacturer"));
const style = Number(form.get("style"));
const name = String(form.get("name"));
const description = String(form.get("description"));
var link : string | null = String(form.get("link")) || null;
if(link == "") link = null;
const abv = Number(form.get("abv"));
const ibu = Number(form.get("ibu"));
const glass = form.has("glass") ? true : false;
const sugar = form.has("sugar") ? true : false;
const gluten = form.has("gluten") ? true : false;
const lactose = form.has("lactose") ? true : false;
const organic = form.has("organic") ? true : false;
try{
await dbe.beer.update({ data: {
slug: slug,
name: name,
description: description,
link: link,
abv: abv,
ibu: ibu,
glass: glass,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
manufacturer: {connect: {id: manufacturer}},
style: {connect: {id: style}}
}, where: {id: id}
});
return redirect("/inventory/beer/" + id);
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const styles = await dbe.beerStyle.findMany({orderBy: {name: "asc"}});
const manufacturers = await dbe.manufacturer.findMany({orderBy: {name: "asc"}});
const beer = await dbe.beer.findUnique({where: {id: id}});
if(!beer) return redirect("/admin/edit/beer");
return json({styles, manufacturers, beer});
}
export default function EditBeerRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit beer ({loaderData.beer.id})</h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.beer.id}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" name="slug" pattern="[a-z0-9_]+" defaultValue={loaderData.beer.slug} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.beer.name} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" type="text" name="description" defaultValue={loaderData.beer.description}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Style</Form.Label>
<Form.Select name="style" required>
{loaderData.styles.map((style) => (
<option value={style.id} selected={loaderData.beer.style_id == style.id}>
{style.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Manufacturer</Form.Label>
<Form.Select name="manufacturer" required>
{loaderData.manufacturers.map((manu) => (
<option value={manu.id} selected={loaderData.beer.manufacturer_id == manu.id}>
{manu.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>ABV</Form.Label>
<InputGroup>
<Form.Control type="number" min={0} max={100} step={0.1} name="abv" defaultValue={loaderData.beer.abv} required/>
<InputGroup.Text>%</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>IBU</Form.Label>
<Form.Control type="number" min={0} max={200} step={1} name="ibu" defaultValue={loaderData.beer.ibu ?? 0} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Glass</Form.Label>
<Form.Check
type="checkbox"
label="Has custom glass"
name="glass"
value={1}
defaultChecked={loaderData.beer.glass}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
defaultChecked={loaderData.beer.sugar}
/>
<Form.Check
type="checkbox"
label="Gluten"
name="gluten"
value={1}
defaultChecked={loaderData.beer.gluten}
/>
<Form.Check
type="checkbox"
label="Lactose"
name="lactose"
value={1}
defaultChecked={loaderData.beer.lactose}
/>
<Form.Check
type="checkbox"
label="Organic"
name="organic"
value={1}
defaultChecked={loaderData.beer.organic}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Link</Form.Label>
<Form.Control type="text" name="link" defaultValue={loaderData.beer.link ?? ""}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+62
View File
@@ -0,0 +1,62 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
//const beers = await dbe.beer.findMany({select: {id: true, name: true, _count: {select: {containers: true}}},orderBy: {name: "asc"}});
const beers = await dbe.beer.findMany({select: {id: true, slug: true, name: true, containers: true},orderBy: {name: "asc"}});
return json({beers});
}
export default function EditBeerRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit beer</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.beers.map((beer) => (
<ListGroup.Item key={beer.id}>
{beer.name} <Badge bg="secondary">{beer.containers.length} Containers</Badge>
<div className="float-end">
{ beer.containers.length <= 0 ? (
<LinkContainer to={"/admin/remove/beer/" + beer.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"/admin/edit/image/beer/" + beer.id}>
<Button variant="secondary" className="mx-3">
Image
</Button>
</LinkContainer>
<LinkContainer to={"" + beer.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
<LinkContainer to={"/inventory/beer/" + beer.slug}>
<Button variant="success" className="mx-3">
View
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+75
View File
@@ -0,0 +1,75 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, Form, ListGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const name = String(form.get("name"));
const color = String(form.get("color")).replaceAll("#", "");
try{
await dbe.beerStyle.update({ data: {name: name, color: color}, where: {id: id} });
return redirect("/admin/edit/beerstyle");
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const style = await dbe.beerStyle.findUnique({where: {id: id}});
if(!style) return redirect("/admin/edit/beerstyle");
return json({style});
}
export default function EditBeerStyleRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit Beer style ({loaderData.style.id})</h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.style.id} />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Style</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.style.name}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Color</Form.Label>
<Form.Control type="color" name="color" defaultValue={"#" + loaderData.style.color} />
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+53
View File
@@ -0,0 +1,53 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { countBy } from "lodash";
import { Container, Row, Col, Button, Form, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const beerstyles = await dbe.beerStyle.findMany({select: {id: true, name: true, color: true, _count: {select: {beers: true}}},orderBy: {name: "asc"}});
return json({beerstyles});
}
export default function EditBeerStyleRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit Beerstyle</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.beerstyles.map((style) => (
<ListGroup.Item key={style.id}>
<span style={{color:"#"+style.color}}>{style.name}</span> <Badge bg="secondary">{style._count.beers} Beers</Badge>
<div className="float-end">
{ style._count.beers <= 0 ? (
<LinkContainer to={"/admin/remove/beerstyle/" + style.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"" + style.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+180
View File
@@ -0,0 +1,180 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, InputGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const slug = String(form.get("slug"));
const manufacturer = Number(form.get("manufacturer"));
const name = String(form.get("name"));
const description = String(form.get("description"));
var link : string | null = String(form.get("link")) || null;
if(link == "") link = null;
const abv = Number(form.get("abv"));
const mix = form.has("mix") ? true : false;
const sugar = form.has("sugar") ? true : false;
const gluten = form.has("gluten") ? true : false;
const lactose = form.has("lactose") ? true : false;
const organic = form.has("organic") ? true : false;
try{
await dbe.cocktail.update({ data: {
slug: slug,
name: name,
description: description,
link: link,
abv: abv,
mix: mix,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
manufacturer: {connect: {id: manufacturer}}
}, where: {id: id}
});
return redirect("/inventory/cocktail/" + id);
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const manufacturers = await dbe.manufacturer.findMany({orderBy: {name: "asc"}});
const cocktail = await dbe.cocktail.findUnique({where: {id: id}});
if(!cocktail) return redirect("/admin/edit/cocktail");
return json({manufacturers, cocktail});
}
export default function EditCocktailRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit cocktail ({loaderData.cocktail.id})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.cocktail.id}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" name="slug" pattern="[a-z0-9_]+" defaultValue={loaderData.cocktail.slug} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.cocktail.name} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" type="text" name="description" defaultValue={loaderData.cocktail.description}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Manufacturer</Form.Label>
<Form.Select name="manufacturer" required>
{loaderData.manufacturers.map((manu) => (
<option value={manu.id} selected={loaderData.cocktail.manufacturer_id == manu.id}>
{manu.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>ABV</Form.Label>
<InputGroup>
<Form.Control type="number" min={0} max={100} step={0.1} name="abv" defaultValue={loaderData.cocktail.abv} required/>
<InputGroup.Text>%</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Cocktail specific</Form.Label>
<Form.Check
type="checkbox"
label="Mix"
name="mix"
value={1}
defaultChecked={loaderData.cocktail.mix}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
defaultChecked={loaderData.cocktail.sugar}
/>
<Form.Check
type="checkbox"
label="Gluten"
name="gluten"
value={1}
defaultChecked={loaderData.cocktail.gluten}
/>
<Form.Check
type="checkbox"
label="Lactose"
name="lactose"
value={1}
defaultChecked={loaderData.cocktail.lactose}
/>
<Form.Check
type="checkbox"
label="Organic"
name="organic"
value={1}
defaultChecked={loaderData.cocktail.organic}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Link</Form.Label>
<Form.Control type="text" name="link" defaultValue={loaderData.cocktail.link ?? ""}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+62
View File
@@ -0,0 +1,62 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
//const cocktails = await dbe.cocktail.findMany({select: {id: true, name: true, _count: {select: {containers: true}}},orderBy: {name: "asc"}});
const cocktails = await dbe.cocktail.findMany({select: {id: true, slug: true, name: true, containers: true},orderBy: {name: "asc"}});
return json({cocktails});
}
export default function EditCocktailRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit cocktail</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.cocktails.map((cocktail) => (
<ListGroup.Item key={cocktail.id}>
{cocktail.name} <Badge bg="secondary">{cocktail.containers.length} Containers</Badge>
<div className="float-end">
{ cocktail.containers.length <= 0 ? (
<LinkContainer to={"/admin/remove/cocktail/" + cocktail.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"/admin/edit/image/cocktail/" + cocktail.id}>
<Button variant="secondary" className="mx-3">
Image
</Button>
</LinkContainer>
<LinkContainer to={"" + cocktail.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
<LinkContainer to={"/inventory/cocktail/" + cocktail.slug}>
<Button variant="success" className="mx-3">
View
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+157
View File
@@ -0,0 +1,157 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, Col, ListGroup, InputGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
import { ContainerType } from "@prisma/client";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
var barcode : string | null = String(form.get("barcode")) || null;
if(barcode == "") barcode = null;
//const drink = Number(form.get("drink"));
const section = Number(form.get("section"));
const type = form.get("type") as ContainerType;
const volume = Number(form.get("volume"));
const portions = Number(form.get("portions"));
const price = Number(form.get("price"));
const inventory = Number(form.get("inventory"));
try{
const updatedContainer = await dbe.container.update({ data: {
barcode: barcode,
type: type,
volume: volume,
portions: portions,
price: price,
inventory: inventory,
//drink: {connect: {id: drink}},
section: {connect: {id: section}}
}, select: {drink: {select: {slug: true, type: true}}},
where: {id: id}
});
return redirect("/inventory/" + updatedContainer.drink.type.toLowerCase() + "/" + updatedContainer.drink.slug);
}
catch(e){
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const drinks = await dbe.drink.findMany({orderBy: {name: "asc"}});
const sections = await dbe.section.findMany({orderBy: {name: "asc"}});
const container = await dbe.container.findUnique({where: {id: id}});
if(!container) return redirect("/admin/edit/container");
return json({drinks, sections, container});
}
export default function EditContainerRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit Container ({loaderData.container.id})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.container.id}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Barcode</Form.Label>
<Form.Control type="text" name="barcode" value={loaderData.container.barcode ?? ""}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Drink</Form.Label>
<Form.Select name="drink" disabled required>
{loaderData.drinks.map((drink) => (
<option value={drink.id} selected={loaderData.container.drink_id == drink.id}>
{drink.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Section</Form.Label>
<Form.Select name="section" required>
{loaderData.sections.map((section) => (
<option value={section.id} selected={loaderData.container.section_id == section.id}>
{section.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Container type</Form.Label>
<Form.Select name="type" required>
<option value={ContainerType.BeerBottle} selected={loaderData.container.type == ContainerType.BeerBottle}>Beer bottle</option>
<option value={ContainerType.Can} selected={loaderData.container.type == ContainerType.Can}>Can</option>
<option value={ContainerType.Carton} selected={loaderData.container.type == ContainerType.Carton}>Carton</option>
<option value={ContainerType.PlasticBottle} selected={loaderData.container.type == ContainerType.PlasticBottle}>Plastic Bottle</option>
<option value={ContainerType.WineBottle} selected={loaderData.container.type == ContainerType.WineBottle}>Wine bottle</option>
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Volume</Form.Label>
<InputGroup>
<Form.Control type="number" min={0} max={2500} step={1} name="volume" defaultValue={loaderData.container.volume} required/>
<InputGroup.Text>ml</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Portions</Form.Label>
<Form.Control type="number" min={1} max={10} step={1} defaultValue={loaderData.container.portions ?? 1} name="portions" required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Price</Form.Label>
<InputGroup>
<InputGroup.Text></InputGroup.Text>
<Form.Control type="number" min={0} max={25} step={0.1} name="price" defaultValue={loaderData.container.price} required/>
</InputGroup>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Inventory</Form.Label>
<Form.Control type="number" min={0} step={1} defaultValue={loaderData.container.inventory} name="inventory" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+55
View File
@@ -0,0 +1,55 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { countBy } from "lodash";
import { Container, Row, Col, Button, Form, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { containerTypeToString } from "~/models/types";
import { volume } from "~/utils/conversions";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const containers = await dbe.container.findMany({select: {id: true, drink: true, barcode: true, type: true, volume: true, inventory: true},orderBy: {drink: {name: "asc"}}});
return json({containers});
}
export default function EditContainerRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit container</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.containers.map((container) => (
<ListGroup.Item key={container.id}>
{container.drink.name} - {containerTypeToString(container.type)} {volume(container.volume)} <Badge bg="secondary">{container.inventory} inventory</Badge>
<div className="float-end">
{ container.inventory <= 0 ? (
<LinkContainer to={"/admin/remove/container/" + container.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"" + container.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+69
View File
@@ -0,0 +1,69 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, Form, ListGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const code = params.code;
const form = await request.formData();
const name = String(form.get("name"));
try{
await dbe.country.update({ data: {name: name}, where: {code: code}});
return redirect("/admin/edit/country");
}
catch(e){
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const code = params.code;
const country = await dbe.country.findUnique({where: {code: code}});
if(!country) return redirect("/admin/edit/country");
return json({country});
}
export default function EditCountryRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit country ({loaderData.country.code})</h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>Code</Form.Label>
<Form.Control type="text" disabled value={loaderData.country.code} />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Country</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.country.name}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+53
View File
@@ -0,0 +1,53 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { countBy } from "lodash";
import { Container, Row, Col, Button, Form, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const countries = await dbe.country.findMany({select: {name: true, code: true, _count: {select: {manufacturers: true}}},orderBy: {name: "asc"}});
return json({countries});
}
export default function EditCountryRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit country</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.countries.map((country) => (
<ListGroup.Item key={country.code}>
{country.name} ({country.code}) <Badge bg="secondary">{country._count.manufacturers} Manufactureres</Badge>
<div className="float-end">
{ country._count.manufacturers <= 0 ? (
<LinkContainer to={"/admin/remove/country/" + country.code}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={country.code}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+102
View File
@@ -0,0 +1,102 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, NodeOnDiskFile, json, redirect, unstable_composeUploadHandlers, unstable_createFileUploadHandler, unstable_createMemoryUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import { renameSync, rmSync } from "node:fs";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, Image } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
file: ({ filename }) => filename.toLowerCase().replaceAll(" ", "_")
}),
// parse everything else into memory
unstable_createMemoryUploadHandler()
);
const parsedForm = await unstable_parseMultipartFormData(
request,
uploadHandler
);
const image = parsedForm.get("image") as NodeOnDiskFile;
const beer = await dbe.beer.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!beer) return redirect("/admin/edit/beer");
const newFilename = "/beers/" + beer.name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
try{
// Remove old image
if(beer.image) rmSync("public"+ beer.image, {force: true});
// Move the file to the beers folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
await dbe.beer.update({data: {image: newFilename}, where: {id: id}});
return redirect("/inventory/beer/" + id);
}
catch(e){
console.log(e);
rmSync(image.getFilePath(), {force: true});
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const beer = await dbe.beer.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!beer) return redirect("/admin/edit/beer");
return json({beer});
}
export default function EditBeerImageRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit beer image ({loaderData.beer.name})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
<Row className="mt-3">
<h3>Current image</h3>
{ loaderData.beer.image ? (
<>
<p>{loaderData.beer.image}</p>
<Image src={loaderData.beer.image}></Image>
</>
) : "No current image" }
</Row>
</Container>
);
}
@@ -0,0 +1,102 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, NodeOnDiskFile, json, redirect, unstable_composeUploadHandlers, unstable_createFileUploadHandler, unstable_createMemoryUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import { renameSync, rmSync } from "node:fs";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, Image } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
file: ({ filename }) => filename.toLowerCase().replaceAll(" ", "_")
}),
// parse everything else into memory
unstable_createMemoryUploadHandler()
);
const parsedForm = await unstable_parseMultipartFormData(
request,
uploadHandler
);
const image = parsedForm.get("image") as NodeOnDiskFile;
const cocktail = await dbe.cocktail.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!cocktail) return redirect("/admin/edit/cocktail");
const newFilename = "/cocktails/" + cocktail.name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
try{
// Remove old image
if(cocktail.image) rmSync("public"+ cocktail.image, {force: true});
// Move the file to the cocktails folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
await dbe.cocktail.update({data: {image: newFilename}, where: {id: id}});
return redirect("/inventory/cocktail/" + id);
}
catch(e){
console.log(e);
rmSync(image.getFilePath(), {force: true});
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const cocktail = await dbe.cocktail.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!cocktail) return redirect("/admin/edit/cocktail");
return json({cocktail});
}
export default function EditCocktailImageRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit cocktail image ({loaderData.cocktail.name})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
<Row className="mt-3">
<h3>Current image</h3>
{ loaderData.cocktail.image ? (
<>
<p>{loaderData.cocktail.image}</p>
<Image src={loaderData.cocktail.image}></Image>
</>
) : "No current image" }
</Row>
</Container>
);
}
@@ -0,0 +1,102 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, NodeOnDiskFile, json, redirect, unstable_composeUploadHandlers, unstable_createFileUploadHandler, unstable_createMemoryUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import { renameSync, rmSync } from "node:fs";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, Image } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
file: ({ filename }) => filename.toLowerCase().replaceAll(" ", "_")
}),
// parse everything else into memory
unstable_createMemoryUploadHandler()
);
const parsedForm = await unstable_parseMultipartFormData(
request,
uploadHandler
);
const image = parsedForm.get("image") as NodeOnDiskFile;
const manufacturer = await dbe.manufacturer.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!manufacturer) return redirect("/admin/edit/manufacturer");
const newFilename = "/manufacturers/" + manufacturer.name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
try{
// Remove old image
if(manufacturer.image) rmSync("public"+ manufacturer.image, {force: true});
// Move the file to the manufacturers folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
await dbe.manufacturer.update({data: {image: newFilename}, where: {id: id}});
return redirect("/inventory/manufacturer/" + id);
}
catch(e){
console.log(e);
rmSync(image.getFilePath(), {force: true});
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const manufacturer = await dbe.manufacturer.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!manufacturer) return redirect("/admin/edit/manufacturer");
return json({manufacturer});
}
export default function EditManufacturerImageRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit Manufacturer image ({loaderData.manufacturer.name})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
<Row className="mt-3">
<h3>Current image</h3>
{ loaderData.manufacturer.image ? (
<>
<p>{loaderData.manufacturer.image}</p>
<Image src={loaderData.manufacturer.image}></Image>
</>
) : "No current image" }
</Row>
</Container>
);
}
+102
View File
@@ -0,0 +1,102 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, NodeOnDiskFile, json, redirect, unstable_composeUploadHandlers, unstable_createFileUploadHandler, unstable_createMemoryUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import { renameSync, rmSync } from "node:fs";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, Image } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
file: ({ filename }) => filename.toLowerCase().replaceAll(" ", "_")
}),
// parse everything else into memory
unstable_createMemoryUploadHandler()
);
const parsedForm = await unstable_parseMultipartFormData(
request,
uploadHandler
);
const image = parsedForm.get("image") as NodeOnDiskFile;
const soda = await dbe.soda.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!soda) return redirect("/admin/edit/soda");
const newFilename = "/soda/" + soda.name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
try{
// Remove old image
if(soda.image) rmSync("public"+ soda.image, {force: true});
// Move the file to the sodas folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
await dbe.soda.update({data: {image: newFilename}, where: {id: id}});
return redirect("/inventory/soda/" + id);
}
catch(e){
console.log(e);
rmSync(image.getFilePath(), {force: true});
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const soda = await dbe.soda.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!soda) return redirect("/admin/edit/soda");
return json({soda});
}
export default function EditSodaImageRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit soda image ({loaderData.soda.name})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
<Row className="mt-3">
<h3>Current image</h3>
{ loaderData.soda.image ? (
<>
<p>{loaderData.soda.image}</p>
<Image src={loaderData.soda.image}></Image>
</>
) : "No current image" }
</Row>
</Container>
);
}
+102
View File
@@ -0,0 +1,102 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, NodeOnDiskFile, json, redirect, unstable_composeUploadHandlers, unstable_createFileUploadHandler, unstable_createMemoryUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import { renameSync, rmSync } from "node:fs";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, Image } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
file: ({ filename }) => filename.toLowerCase().replaceAll(" ", "_")
}),
// parse everything else into memory
unstable_createMemoryUploadHandler()
);
const parsedForm = await unstable_parseMultipartFormData(
request,
uploadHandler
);
const image = parsedForm.get("image") as NodeOnDiskFile;
const wine = await dbe.wine.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!wine) return redirect("/admin/edit/wine");
const newFilename = "/wine/" + wine.name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
try{
// Remove old image
if(wine.image) rmSync("public"+ wine.image, {force: true});
// Move the file to the wines folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
await dbe.wine.update({data: {image: newFilename}, where: {id: id}});
return redirect("/inventory/wine/" + id);
}
catch(e){
console.log(e);
rmSync(image.getFilePath(), {force: true});
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const wine = await dbe.wine.findUnique({select: {image: true, name: true}, where: {id: id}});
if(!wine) return redirect("/admin/edit/wine");
return json({wine});
}
export default function EditWineImageRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit wine image ({loaderData.wine.name})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
<Row className="mt-3">
<h3>Current image</h3>
{ loaderData.wine.image ? (
<>
<p>{loaderData.wine.image}</p>
<Image src={loaderData.wine.image}></Image>
</>
) : "No current image" }
</Row>
</Container>
);
}
@@ -0,0 +1,89 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const country = String(form.get("country"));
const name = String(form.get("name"));
const description = String(form.get("description"));
try{
await dbe.manufacturer.update({data: {name: name, description: description, country: {connect: {code: country}}}, where: {id: id} });
return redirect("/inventory/manufacturer/" + id);
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const countries = await dbe.country.findMany({orderBy: {name: "asc"}});
const manufacturer = await dbe.manufacturer.findUnique({where: {id: id}});
if(!manufacturer) return redirect("/admin/edit/manufacturer");
return json({manufacturer, countries});
}
export default function NewManufacturerRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit Manufacturer ({loaderData.manufacturer.id})</h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.manufacturer.id} />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Country</Form.Label>
<Form.Select name="country" required>
{loaderData.countries.map((country) => (
<option value={country.code} selected={loaderData.manufacturer.country_id == country.code}>
{country.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.manufacturer.name} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" type="text" name="description" defaultValue={loaderData.manufacturer.description ?? ""}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+58
View File
@@ -0,0 +1,58 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { countBy } from "lodash";
import { Container, Row, Col, Button, Form, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const manufacturers = await dbe.manufacturer.findMany({select: {id: true, name: true, _count: {select: {drinks: true}}},orderBy: {name: "asc"}});
return json({manufacturers});
}
export default function EditManufacturerRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit manufacturer</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.manufacturers.map((manufacturer) => (
<ListGroup.Item key={manufacturer.id}>
{manufacturer.name} <Badge bg="secondary">{manufacturer._count.drinks} Drinks</Badge>
<div className="float-end">
{ manufacturer._count.drinks <= 0 ? (
<LinkContainer to={"/admin/remove/manufacturer/" + manufacturer.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"/admin/edit/image/manufacturer/" + manufacturer.id}>
<Button variant="secondary" className="mx-3">
Image
</Button>
</LinkContainer>
<LinkContainer to={"" + manufacturer.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+15
View File
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.beer.delete({where: {id: id}});
return redirect("/admin/edit/beer");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.beerStyle.delete({where: {id: id}});
return redirect("/admin/edit/beerstyle");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.cocktail.delete({where: {id: id}});
return redirect("/admin/edit/cocktail");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.container.delete({where: {id: id}});
return redirect("/admin/edit/container");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const code = params.code;
await dbe.country.delete({where: {code: code}});
return redirect("/admin/edit/country");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.manufacturer.delete({where: {id: id}});
return redirect("/admin/edit/manufacturer");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.section.delete({where: {id: id}});
return redirect("/admin/edit/section");
}
+15
View File
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.soda.delete({where: {id: id}});
return redirect("/admin/edit/soda");
}
+15
View File
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.wine.delete({where: {id: id}});
return redirect("/admin/edit/wine");
}
@@ -0,0 +1,15 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
await dbe.wineStyle.delete({where: {id: id}});
return redirect("/admin/edit/winestyle");
}
+22
View File
@@ -0,0 +1,22 @@
import { Link } from "@remix-run/react";
import { ListGroup } from "react-bootstrap";
export default function AdminRoute() {
return (
<div>
<h1>Edit or remove</h1>
<ListGroup>
<ListGroup.Item><Link to="/admin/edit/country">country</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/section">section</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/beerstyle">beer style</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/winestyle">wine style</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/manufacturer">manufacturer</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/beer">beer</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/wine">wine</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/soda">soda</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/cocktail">cocktail</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/container">container</Link></ListGroup.Item>
</ListGroup>
</div>
);
}
+68
View File
@@ -0,0 +1,68 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, Form, ListGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const name = String(form.get("name"));
try{
await dbe.section.update({ data: {name: name}, where: {id: id} });
return redirect("/admin/edit/section");
}
catch(e){
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const section = await dbe.section.findUnique({where: {id: id}});
if(!section) return redirect("/admin/edit/section");
return json({section});
}
export default function EditSectionRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit Section ({loaderData.section.id})</h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.section.id} />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Section</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.section.name}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+53
View File
@@ -0,0 +1,53 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { countBy } from "lodash";
import { Container, Row, Col, Button, Form, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const sections = await dbe.section.findMany({select: {name: true, id: true, _count: {select: {containers: true}}}, orderBy: {name: "asc"}});
return json({sections});
}
export default function EditSectionRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit section</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.sections.map((section) => (
<ListGroup.Item key={section.id}>
{section.name} <Badge bg="secondary">{section._count.containers} Containers</Badge>
<div className="float-end">
{ section._count.containers <= 0 ? (
<LinkContainer to={"/admin/remove/section/" + section.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"" + section.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+180
View File
@@ -0,0 +1,180 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, InputGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const slug = String(form.get("slug"));
const manufacturer = Number(form.get("manufacturer"));
const name = String(form.get("name"));
const description = String(form.get("description"));
var link : string | null = String(form.get("link")) || null;
if(link == "") link = null;
const abv = Number(form.get("abv"));
const carbonated = form.has("carbonated") ? true : false;
const sugar = form.has("sugar") ? true : false;
const gluten = form.has("gluten") ? true : false;
const lactose = form.has("lactose") ? true : false;
const organic = form.has("organic") ? true : false;
try{
await dbe.soda.update({ data: {
slug: slug,
name: name,
description: description,
link: link,
abv: abv,
carbonated: carbonated,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
manufacturer: {connect: {id: manufacturer}}
}, where: { id: id}
});
return redirect("/inventory/soda/" + id);
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const manufacturers = await dbe.manufacturer.findMany({orderBy: {name: "asc"}});
const soda = await dbe.soda.findUnique({where: {id: id}});
if(!soda) return redirect("/admin/edit/soda");
return json({manufacturers, soda});
}
export default function EditSodaRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit soda ({loaderData.soda.id})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.soda.id}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" name="slug" pattern="[a-z0-9_]+" defaultValue={loaderData.soda.slug} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.soda.name} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" type="text" name="description" defaultValue={loaderData.soda.description}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Manufacturer</Form.Label>
<Form.Select name="manufacturer" required>
{loaderData.manufacturers.map((manu) => (
<option value={manu.id} selected={loaderData.soda.manufacturer_id == manu.id}>
{manu.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>ABV</Form.Label>
<InputGroup>
<Form.Control type="number" min={0} max={100} step={0.1} name="abv" defaultValue={loaderData.soda.abv} required/>
<InputGroup.Text>%</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Soda specific</Form.Label>
<Form.Check
type="checkbox"
label="Carbonated"
name="carbonated"
value={1}
defaultChecked={loaderData.soda.carbonated}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
defaultChecked={loaderData.soda.sugar}
/>
<Form.Check
type="checkbox"
label="Gluten"
name="gluten"
value={1}
defaultChecked={loaderData.soda.gluten}
/>
<Form.Check
type="checkbox"
label="Lactose"
name="lactose"
value={1}
defaultChecked={loaderData.soda.lactose}
/>
<Form.Check
type="checkbox"
label="Organic"
name="organic"
value={1}
defaultChecked={loaderData.soda.organic}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Link</Form.Label>
<Form.Control type="text" name="link" defaultValue={loaderData.soda.link ?? ""}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+62
View File
@@ -0,0 +1,62 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
//const sodas = await dbe.soda.findMany({select: {id: true, name: true, _count: {select: {containers: true}}},orderBy: {name: "asc"}});
const sodas = await dbe.soda.findMany({select: {id: true, slug: true, name: true, containers: true},orderBy: {name: "asc"}});
return json({sodas});
}
export default function EditSodaRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit soda</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.sodas.map((soda) => (
<ListGroup.Item key={soda.id}>
{soda.name} <Badge bg="secondary">{soda.containers.length} Containers</Badge>
<div className="float-end">
{ soda.containers.length <= 0 ? (
<LinkContainer to={"/admin/remove/soda/" + soda.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"/admin/edit/image/soda/" + soda.id}>
<Button variant="secondary" className="mx-3">
Image
</Button>
</LinkContainer>
<LinkContainer to={"" + soda.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
<LinkContainer to={"/inventory/soda/" + soda.slug}>
<Button variant="success" className="mx-3">
View
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+227
View File
@@ -0,0 +1,227 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Button, Form, InputGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const slug = String(form.get("slug"));
const manufacturer = Number(form.get("manufacturer"));
const style = Number(form.get("style"));
const name = String(form.get("name"));
const description = String(form.get("description"));
var link : string | null = String(form.get("link")) || null;
if(link == "") link = null;
const abv = Number(form.get("abv"));
var heavy_score : number | null = Number(form.get("heavy_score"));
if(heavy_score < 0) heavy_score = null;
var tannine_score : number | null = Number(form.get("tannine_score"));
if(tannine_score < 0) tannine_score = null;
var dry_score : number | null = Number(form.get("dry_score"));
if(dry_score < 0) dry_score = null;
var fresh_score : number | null = Number(form.get("fresh_score"));
if(fresh_score < 0) fresh_score = null;
var notes : string | null = String(form.get("notes")) || null;
if(notes == "") notes = null;
const sugar = form.has("sugar") ? true : false;
const gluten = form.has("gluten") ? true : false;
const lactose = form.has("lactose") ? true : false;
const organic = form.has("organic") ? true : false;
try{
await dbe.wine.update({ data: {
slug: slug,
name: name,
description: description,
link: link,
abv: abv,
heavy_score: heavy_score,
tannine_score: tannine_score,
dry_score: dry_score,
fresh_score: fresh_score,
notes: notes,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
manufacturer: {connect: {id: manufacturer}},
style: {connect: {id: style}}
}, where: {id: id}
});
return redirect("/inventory/wine/" + id);
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const styles = await dbe.wineStyle.findMany({orderBy: {name: "asc"}});
const manufacturers = await dbe.manufacturer.findMany({orderBy: {name: "asc"}});
const wine = await dbe.wine.findUnique({where: {id: id}});
if(!wine) return redirect("/admin/edit/wine");
return json({styles, manufacturers, wine});
}
export default function EditWineRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit wine ({loaderData.wine.id})</h1>
<Form method="POST" encType="multipart/form-data">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.wine.id}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" name="slug" pattern="[a-z0-9_]+" defaultValue={loaderData.wine.slug} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.wine.name} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" type="text" name="description" defaultValue={loaderData.wine.description}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Style</Form.Label>
<Form.Select name="style" required>
{loaderData.styles.map((style) => (
<option value={style.id} selected={loaderData.wine.style_id == style.id}>
{style.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Manufacturer</Form.Label>
<Form.Select name="manufacturer" required>
{loaderData.manufacturers.map((manu) => (
<option value={manu.id} selected={loaderData.wine.manufacturer_id == manu.id}>
{manu.name}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>ABV</Form.Label>
<InputGroup>
<Form.Control type="number" min={0} max={100} step={0.1} name="abv" defaultValue={loaderData.wine.abv} required/>
<InputGroup.Text>%</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Heavy</Form.Label>
<Form.Range name="heavy_score" min={-1} max={5} step={1} defaultValue={loaderData.wine.heavy_score ?? -1}/>
<Form.Text>Left = undefined, then light (0) - bold (5) </Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Tannine</Form.Label>
<Form.Range name="tannine_score" min={-1} max={5} step={1} defaultValue={loaderData.wine.tannine_score ?? -1}/>
<Form.Text>Left = undefined, then smooth (0) - tannic (5) </Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Dryness</Form.Label>
<Form.Range name="dry_score" min={-1} max={5} step={1} defaultValue={loaderData.wine.dry_score ?? -1}/>
<Form.Text>Left = undefined, then dry (0) - sweet (5) </Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Freshness</Form.Label>
<Form.Range name="fresh_score" min={-1} max={5} step={1} defaultValue={loaderData.wine.fresh_score ?? -1}/>
<Form.Text>Left = undefined, then soft (0) - acidic (5) </Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Notes</Form.Label>
<Form.Control as="textarea" type="text" name="notes" defaultValue={loaderData.wine.notes ?? ""}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
defaultChecked={loaderData.wine.sugar}
/>
<Form.Check
type="checkbox"
label="Gluten"
name="gluten"
value={1}
defaultChecked={loaderData.wine.gluten}
/>
<Form.Check
type="checkbox"
label="Lactose"
name="lactose"
value={1}
defaultChecked={loaderData.wine.lactose}
/>
<Form.Check
type="checkbox"
label="Organic"
name="organic"
value={1}
defaultChecked={loaderData.wine.organic}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Link</Form.Label>
<Form.Control type="text" name="link" defaultValue={loaderData.wine.link ?? ""}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+62
View File
@@ -0,0 +1,62 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
//const wines = await dbe.wine.findMany({select: {id: true, name: true, _count: {select: {containers: true}}},orderBy: {name: "asc"}});
const wines = await dbe.wine.findMany({select: {id: true, slug: true, name: true, containers: true}, orderBy: {name: "asc"}});
return json({wines});
}
export default function EditWineRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit wine</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.wines.map((wine) => (
<ListGroup.Item key={wine.id}>
{wine.name} <Badge bg="secondary">{wine.containers.length} Containers</Badge>
<div className="float-end">
{ wine.containers.length <= 0 ? (
<LinkContainer to={"/admin/remove/wine/" + wine.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"/admin/edit/image/wine/" + wine.id}>
<Button variant="secondary" className="mx-3">
Image
</Button>
</LinkContainer>
<LinkContainer to={"" + wine.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
<LinkContainer to={"/inventory/wine/" + wine.slug}>
<Button variant="success" className="mx-3">
View
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+75
View File
@@ -0,0 +1,75 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, Form, ListGroup } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
export const action = async ({
request,
params
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const form = await request.formData();
const name = String(form.get("name"));
const color = String(form.get("color")).replaceAll("#", "");
try{
await dbe.wineStyle.update({ data: {name: name, color: color}, where: {id: id} });
return redirect("/admin/edit/winestyle");
}
catch(e){
console.log(e);
}
return null;
};
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const style = await dbe.wineStyle.findUnique({where: {id: id}});
if(!style) return redirect("/admin/edit/winestyle");
return json({style});
}
export default function EditWineStyleRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<Row>
<h1>Edit Wine style ({loaderData.style.id})</h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>ID</Form.Label>
<Form.Control type="text" disabled value={loaderData.style.id} />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Style</Form.Label>
<Form.Control type="text" name="name" defaultValue={loaderData.style.name}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Color</Form.Label>
<Form.Control type="color" name="color" defaultValue={"#" + loaderData.style.color} />
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+51
View File
@@ -0,0 +1,51 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Container, Row, Col, Button, ListGroup, Badge } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const winestyles = await dbe.wineStyle.findMany({select: {id: true, name: true, color: true, _count: {select: {wines: true}}},orderBy: {name: "asc"}});
return json({winestyles});
}
export default function EditWineStyleRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Edit Winestyle</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.winestyles.map((style) => (
<ListGroup.Item key={style.id}>
<span style={{color:"#"+style.color}}>{style.name}</span> <Badge bg="secondary">{style._count.wines} Wines</Badge>
<div className="float-end">
{ style._count.wines <= 0 ? (
<LinkContainer to={"/admin/remove/winestyle/" + style.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
) : "" }
<LinkContainer to={"" + style.id}>
<Button variant="primary" className="mx-3">
Edit
</Button>
</LinkContainer>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+27 -4
View File
@@ -35,17 +35,22 @@ export const action = async ({
const abv = Number(parsedForm.get("abv"));
const ibu = Number(parsedForm.get("ibu"));
const glass = parsedForm.has("glass") ? true : false;
const sugar = parsedForm.has("sugar") ? true : false;
const gluten = parsedForm.has("gluten") ? true : false;
const lactose = parsedForm.has("lactose") ? true : false;
const organic = parsedForm.has("organic") ? true : false;
const image = parsedForm.get("image") as NodeOnDiskFile;
const newFilename = "/beers/" + slug + "." + image.name.split('.').pop();
var newFilename = null;
if(image.size > 0)
newFilename = "/beers/" + slug + "." + image.name.split('.').pop();
try{
// Move the file to the beers folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
if(newFilename) renameSync(image.getFilePath(), "public" + newFilename);
const createdBeer = await dbe.beer.create({ data: {
slug: slug,
@@ -55,6 +60,8 @@ export const action = async ({
link: link,
abv: abv,
ibu: ibu,
glass: glass,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
@@ -94,7 +101,7 @@ export default function NewBeerRoute() {
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" placeholder="blond_beer" name="slug" pattern="[a-z_]+" required/>
<Form.Control type="text" placeholder="blond_beer" name="slug" pattern="[a-z0-9_]+" required/>
</Form.Group>
<Form.Group className="mb-3">
@@ -143,8 +150,24 @@ export default function NewBeerRoute() {
<Form.Control type="number" min={0} max={200} step={1} name="ibu" defaultValue={0} required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Glass</Form.Label>
<Form.Check
type="checkbox"
label="Has custom glass"
name="glass"
value={1}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
/>
<Form.Check
type="checkbox"
label="Gluten"
@@ -172,7 +195,7 @@ export default function NewBeerRoute() {
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
<Form.Control type="file" name="image" accept="image/*"/>
</Form.Group>
<Button variant="primary" type="submit">
+14 -4
View File
@@ -35,17 +35,20 @@ export const action = async ({
const mix = parsedForm.has("mix") ? true : false;
const sugar = parsedForm.has("sugar") ? true : false;
const gluten = parsedForm.has("gluten") ? true : false;
const lactose = parsedForm.has("lactose") ? true : false;
const organic = parsedForm.has("organic") ? true : false;
const image = parsedForm.get("image") as NodeOnDiskFile;
const newFilename = "/cocktails/" + slug + "." + image.name.split('.').pop();
var newFilename = null;
if(image.size > 0)
newFilename = "/cocktails/" + slug + "." + image.name.split('.').pop();
try{
// Move the file to the beers folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
if(newFilename) renameSync(image.getFilePath(), "public" + newFilename);
const createdCocktail = await dbe.cocktail.create({ data: {
slug: slug,
@@ -55,6 +58,7 @@ export const action = async ({
link: link,
abv: abv,
mix: mix,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
@@ -92,7 +96,7 @@ export default function NewCocktailRoute() {
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" placeholder="some_cocktail_drink" name="slug" pattern="[a-z_]+" required/>
<Form.Control type="text" placeholder="some_cocktail_drink" name="slug" pattern="[a-z0-9_]+" required/>
</Form.Group>
<Form.Group className="mb-3">
@@ -137,6 +141,12 @@ export default function NewCocktailRoute() {
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
/>
<Form.Check
type="checkbox"
label="Gluten"
@@ -164,7 +174,7 @@ export default function NewCocktailRoute() {
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
<Form.Control type="file" name="image" accept="image/*"/>
</Form.Group>
<Button variant="primary" type="submit">
+5 -3
View File
@@ -28,11 +28,13 @@ export const action = async ({
const description = String(parsedForm.get("description"));
const image = parsedForm.get("image") as NodeOnDiskFile;
const newFilename = "/manufacturers/" + name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
var newFilename = null;
if(image.size > 0)
newFilename = "/manufacturers/" + name.toLowerCase().replaceAll(" ", "_") + "." + image.name.split('.').pop();
try{
// Move the file to the manufacturers folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
if(newFilename) renameSync(image.getFilePath(), "public" + newFilename);
const createdManufacturer = await dbe.manufacturer.create({ data: {name: name, description: description, image: newFilename, country: {connect: {code: country}}} });
return redirect("/inventory/manufacturer/" + createdManufacturer.id);
@@ -88,7 +90,7 @@ export default function NewManufacturerRoute() {
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
<Form.Control type="file" name="image" accept="image/*"/>
</Form.Group>
<Button variant="primary" type="submit">
+16 -6
View File
@@ -34,18 +34,21 @@ export const action = async ({
const abv = Number(parsedForm.get("abv"));
const carbonated = parsedForm.has("carbonated") ? true : false;
const gluten = parsedForm.has("gluten") ? true : false;
const sugar = parsedForm.has("sugar") ? true : false;
const gluten = parsedForm.has("gluten") ? true : false;
const lactose = parsedForm.has("lactose") ? true : false;
const organic = parsedForm.has("organic") ? true : false;
const image = parsedForm.get("image") as NodeOnDiskFile;
const newFilename = "/soda/" + slug + "." + image.name.split('.').pop();
var newFilename = null;
if(image.size > 0)
newFilename = "/soda/" + slug + "." + image.name.split('.').pop();
try{
// Move the file to the beers folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
if(newFilename) renameSync(image.getFilePath(), "public" + newFilename);
const createdSoda = await dbe.soda.create({ data: {
slug: slug,
@@ -55,6 +58,7 @@ export const action = async ({
link: link,
abv: abv,
carbonated: carbonated,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
@@ -92,7 +96,7 @@ export default function NewSodaRoute() {
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" placeholder="some_soda_drink" name="slug" pattern="[a-z_]+" required/>
<Form.Control type="text" placeholder="some_soda_drink" name="slug" pattern="[a-z0-9_]+" required/>
</Form.Group>
<Form.Group className="mb-3">
@@ -137,6 +141,12 @@ export default function NewSodaRoute() {
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
/>
<Form.Check
type="checkbox"
label="Gluten"
@@ -164,7 +174,7 @@ export default function NewSodaRoute() {
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
<Form.Control type="file" name="image" accept="image/*"/>
</Form.Group>
<Button variant="primary" type="submit">
+14 -4
View File
@@ -46,17 +46,20 @@ export const action = async ({
var notes : string | null = String(parsedForm.get("notes")) || null;
if(notes == "") notes = null;
const sugar = parsedForm.has("sugar") ? true : false;
const gluten = parsedForm.has("gluten") ? true : false;
const lactose = parsedForm.has("lactose") ? true : false;
const organic = parsedForm.has("organic") ? true : false;
const image = parsedForm.get("image") as NodeOnDiskFile;
const newFilename = "/wine/" + slug + "." + image.name.split('.').pop();
var newFilename = null;
if(image.size > 0)
newFilename = "/wine/" + slug + "." + image.name.split('.').pop();
try{
// Move the file to the wines folder with the slug as name
renameSync(image.getFilePath(), "public" + newFilename);
if(newFilename) renameSync(image.getFilePath(), "public" + newFilename);
const createdWine = await dbe.wine.create({ data: {
slug: slug,
@@ -70,6 +73,7 @@ export const action = async ({
dry_score: dry_score,
fresh_score: fresh_score,
notes: notes,
sugar: sugar,
lactose: lactose,
gluten: gluten,
organic: organic,
@@ -109,7 +113,7 @@ export default function NewWineRoute() {
<Form.Group className="mb-3">
<Form.Label>Slug</Form.Label>
<Form.Control type="text" placeholder="sauvignon_blanc" name="slug" pattern="[a-z_]+" required/>
<Form.Control type="text" placeholder="sauvignon_blanc" name="slug" pattern="[a-z0-9_]+" required/>
</Form.Group>
<Form.Group className="mb-3">
@@ -184,6 +188,12 @@ export default function NewWineRoute() {
<Form.Group className="mb-3">
<Form.Label>Allergies</Form.Label>
<Form.Check
type="checkbox"
label="Sugar"
name="sugar"
value={1}
/>
<Form.Check
type="checkbox"
label="Gluten"
@@ -211,7 +221,7 @@ export default function NewWineRoute() {
<Form.Group className="mb-3">
<Form.Label>Image</Form.Label>
<Form.Control type="file" name="image" accept="image/*" required/>
<Form.Control type="file" name="image" accept="image/*"/>
</Form.Group>
<Button variant="primary" type="submit">
+1 -1
View File
@@ -45,7 +45,7 @@ export default function NewWineStyleRoute() {
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>Style</Form.Label>
<Form.Control type="text" placeholder="Dark ale" name="name" />
<Form.Control type="text" placeholder="Light wine" name="name" />
</Form.Group>
<Form.Group className="mb-3">
+3 -2
View File
@@ -7,8 +7,9 @@ export default function AdminRoute() {
<h1>Admin</h1>
<ListGroup>
<ListGroup.Item><Link to="/admin/new">Add new items</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/inventory">Change inventory numbers</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit/checkout">Undo checkout</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/edit">Edit or remove items</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/manage/inventory">Change inventory numbers</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/manage/checkout">Undo checkout</Link></ListGroup.Item>
</ListGroup>
</div>
);
+29 -9
View File
@@ -1,14 +1,9 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useLoaderData } from "@remix-run/react";
import { enhance } from "@zenstackhq/runtime";
import { includes } from "lodash";
import { Accordion, Button, Col, Container, Row, useAccordionButton } from "react-bootstrap";
import DrinkCard from "~/components/cards/drink.card";
import DrinkFilter from "~/components/filters/drink.filter";
import { findDrinksFromSearch } from "~/models/drinks.filter.server";
import { findManufacturersFromSearch } from "~/models/drinks.server";
import { sortDrinksFromSearch } from "~/models/drinks.sort.server";
import { Col, Container, Row, Table } from "react-bootstrap";
import { ContainerWithDrink } from "~/models/types";
import timeAgo from "~/utils/datetime";
import { db } from "~/utils/db.server";
@@ -79,7 +74,9 @@ export const loader = async ({request} : LoaderFunctionArgs) => {
const statsLast24H = statsForContainers(containersLast24);
return json({total, statsLast24H});
const history = await dbe.history.findMany({select: {checkoutAt: true, inventoryAfter: true, container: {select: {drink: true}}}, take: 10, orderBy: {checkoutAt: "desc"}});
return json({total, statsLast24H, history});
};
export default function StatsRoute() {
@@ -111,6 +108,29 @@ export default function StatsRoute() {
Average alcohol percentage: {loadData.statsLast24H.averageAlcoholPercentage}% <br />
</Col>
</Row>
<Row className="mt-3">
<Col xs={12}>
<h3>History</h3>
<Table striped>
<thead>
<tr>
<th>Drink</th>
<th>When</th>
<th>Amount left</th>
</tr>
</thead>
<tbody>
{loadData.history.map((entry) => (
<tr key={entry.container.drink.slug}>
<td>{entry.container.drink.name}</td>
<td>{timeAgo(new Date(entry.checkoutAt))}</td>
<td>{entry.inventoryAfter}</td>
</tr>
))}
</tbody>
</Table>
</Col>
</Row>
</Container>
);
}
+1 -1
View File
@@ -86,7 +86,7 @@ export default function ScanRoute() {
<Table striped>
<thead>
<tr>
<th>Beer</th>
<th>Drink</th>
<th>When</th>
<th>Amount left</th>
</tr>
+8
View File
@@ -0,0 +1,8 @@
export function volume(volume: number) : string {
if(volume < 1000){
return "" + volume + "ml";
}
return "" + (volume / 1000) + "l"
}
+2
View File
@@ -51,6 +51,7 @@ model Drink {
gluten Boolean
lactose Boolean
organic Boolean
sugar Boolean @default(true)
containers Container[]
delegate_aux_beer Beer?
delegate_aux_wine Wine?
@@ -67,6 +68,7 @@ model Beer {
style_id Int
style BeerStyle @relation(fields: [style_id], references: [id])
ibu Float?
glass Boolean @default(false)
delegate_aux_drink Drink @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
}
+3
View File
@@ -50,6 +50,7 @@ model Drink {
gluten Boolean
lactose Boolean
organic Boolean
sugar Boolean @default(true)
containers Container[]
@@ -65,6 +66,8 @@ model Beer extends Drink {
ibu Float?
glass Boolean @default(false)
@@allow('read', true)
@@allow('all', auth().type == Admin)
}
+56 -2
View File
@@ -9,12 +9,15 @@ export default defineConfig({
plugins: [remix({
routes(defineRoutes) {
return defineRoutes((route) => {
// Index
route("/", "routes/_index.tsx", { index: true });
// Resources
route("/resource/checkouts", "routes/resource/checkouts.tsx");
// Inventory
route("inventory", "routes/inventory/layout.tsx", () => {
route("", "routes/inventory/route.tsx", { index: true });
@@ -28,15 +31,18 @@ export default defineConfig({
});
// Scanner
route("scan", "routes/scan/layout.tsx", () => {
route("", "routes/scan/route.tsx", { index: true });
route("link/:barcode", "routes/scan/link.$barcode.tsx");
});
// Admin
route("admin", "routes/admin/layout.tsx", () => {
route("", "routes/admin/route.tsx", { index: true });
route("new", "routes/admin/new/route.tsx");
route("new/country", "routes/admin/new/country.tsx");
route("new/section", "routes/admin/new/section.tsx");
@@ -48,8 +54,56 @@ export default defineConfig({
route("new/cocktail", "routes/admin/new/cocktail.tsx");
route("new/container", "routes/admin/new/container.tsx");
route("new/manufacturer","routes/admin/new/manufacturer.tsx");
route("edit/inventory", "routes/admin/edit/inventory.tsx");
route("edit/checkout", "routes/admin/edit/checkout.tsx");
route("edit", "routes/admin/edit/route.tsx");
route("edit/country", "routes/admin/edit/country.tsx");
route("edit/country/:code", "routes/admin/edit/country.$code.tsx");
route("remove/country/:code", "routes/admin/edit/remove/country.$code.tsx");
route("edit/section", "routes/admin/edit/section.tsx");
route("edit/section/:id", "routes/admin/edit/section.$id.tsx");
route("remove/section/:id", "routes/admin/edit/remove/section.$id.tsx");
route("edit/beerstyle", "routes/admin/edit/beerstyle.tsx");
route("edit/beerstyle/:id", "routes/admin/edit/beerstyle.$id.tsx");
route("remove/beerstyle/:id", "routes/admin/edit/remove/beerstyle.$id.tsx");
route("edit/winestyle", "routes/admin/edit/winestyle.tsx");
route("edit/winestyle/:id", "routes/admin/edit/winestyle.$id.tsx");
route("remove/winestyle/:id", "routes/admin/edit/remove/winestyle.$id.tsx");
route("edit/manufacturer", "routes/admin/edit/manufacturer.tsx");
route("edit/manufacturer/:id", "routes/admin/edit/manufacturer.$id.tsx");
route("remove/manufacturer/:id", "routes/admin/edit/remove/manufacturer.$id.tsx");
route("edit/image/manufacturer/:id", "routes/admin/edit/image/manufacturer.$id.tsx");
route("edit/beer", "routes/admin/edit/beer.tsx");
route("edit/beer/:id", "routes/admin/edit/beer.$id.tsx");
route("remove/beer/:id", "routes/admin/edit/remove/beer.$id.tsx");
route("edit/image/beer/:id", "routes/admin/edit/image/beer.$id.tsx");
route("edit/wine", "routes/admin/edit/wine.tsx");
route("edit/wine/:id", "routes/admin/edit/wine.$id.tsx");
route("remove/wine/:id", "routes/admin/edit/remove/wine.$id.tsx");
route("edit/image/wine/:id", "routes/admin/edit/image/wine.$id.tsx");
route("edit/soda", "routes/admin/edit/soda.tsx");
route("edit/soda/:id", "routes/admin/edit/soda.$id.tsx");
route("remove/soda/:id", "routes/admin/edit/remove/soda.$id.tsx");
route("edit/image/soda/:id", "routes/admin/edit/image/soda.$id.tsx");
route("edit/cocktail", "routes/admin/edit/cocktail.tsx");
route("edit/cocktail/:id", "routes/admin/edit/cocktail.$id.tsx");
route("remove/cocktail/:id", "routes/admin/edit/remove/cocktail.$id.tsx");
route("edit/image/cocktail/:id", "routes/admin/edit/image/cocktail.$id.tsx");
route("edit/container", "routes/admin/edit/container.tsx");
route("edit/container/:id", "routes/admin/edit/container.$id.tsx");
route("remove/container/:id", "routes/admin/edit/remove/container.$id.tsx");
route("manage/inventory", "routes/admin/manage/inventory.tsx");
route("manage/checkout", "routes/admin/manage/checkout.tsx");
});
});
},