diff --git a/app/components/filters/drink.filter.tsx b/app/components/filters/drink.filter.tsx index 1bc4369..7c7f7b6 100644 --- a/app/components/filters/drink.filter.tsx +++ b/app/components/filters/drink.filter.tsx @@ -69,10 +69,10 @@ function DrinkFilter(arg : Arguments) { ["name", "Name"], ["new", "New"], ["popular", "Popularity"], - ["abv-asc", "ABV Low -> High"], - ["abv-desc", "ABV High -> Low"], - ["inv-asc", "Inventoy Low -> High"], - ["inv-desc", "Inventory High -> Low"] + ["abv-asc", "Alcohol (Low -> High)"], + ["abv-desc", "Alcohol (High -> Low)"], + ["inv-asc", "Inventory (Low -> High)"], + ["inv-desc", "Inventory (High -> Low)"] ].map((pair) => ( - Drink type + Drink Type { ["Beer", "Wine", "Soda", "Cocktail"].map(key => ( - ABV -
{abvOptionsMarkup}
+ Alcohol Percentage +
{abvOptionsMarkup}
{/* Manufacturer diff --git a/app/routes/admin/manage/inventory.tsx b/app/routes/admin/manage/inventory.tsx index 385ed95..2288c79 100644 --- a/app/routes/admin/manage/inventory.tsx +++ b/app/routes/admin/manage/inventory.tsx @@ -1,8 +1,10 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; import { json } from "@remix-run/node"; +import { useMemo, useState } from "react"; import { useLoaderData } from "@remix-run/react"; import { Button, Form } from "react-bootstrap"; import { containerTypeToString } from "~/models/types"; +import { volume } from '~/utils/conversions'; import { enhance } from "~/utils/db.server"; @@ -12,7 +14,7 @@ export async function loader({ }: LoaderFunctionArgs) { const { dbe } = await enhance(request); - const containers = await dbe.container.findMany({select: {id: true, drink: true, type: true, inventory: true}, orderBy: {drink: {name: "asc"}}}); + const containers = await dbe.container.findMany({select: {id: true, drink: true, type: true, inventory: true, volume: true}, orderBy: {drink: {name: "asc"}}}); return json({ containers }); }; @@ -44,16 +46,37 @@ export async function action({ export default function EditInventoryRoute() { const lData = useLoaderData(); + const [search, setSearch] = useState(""); + + const filteredContainers = useMemo(() => { + const searchText = search.trim().toLowerCase(); + if (!searchText) return lData.containers; + + return lData.containers.filter((container) => { + const label = `${container.drink.name} ${containerTypeToString(container.type)} ${container.inventory}`.toLowerCase(); + return label.includes(searchText); + }); + }, [lData.containers, search]); return (

Update inventory

+ + Filter containers + setSearch(event.currentTarget.value)} + placeholder="Type drink name, type or inventory" + autoComplete="off" + /> + Drink container - { lData.containers.map((container) => ( - + { filteredContainers.map((container) => ( + ))} diff --git a/app/routes/scan/route.tsx b/app/routes/scan/route.tsx index 599247f..fa1244a 100644 --- a/app/routes/scan/route.tsx +++ b/app/routes/scan/route.tsx @@ -1,6 +1,7 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; import { useActionData, useLoaderData } from "@remix-run/react"; +import { useEffect, useRef, useState } from "react"; import { Col, Container, Form, Row, Table } from "react-bootstrap"; import timeAgo from "~/utils/datetime"; @@ -27,9 +28,13 @@ export async function action({ const form = await request.formData(); const barcode = form.get("barcode")?.toString() || ""; - const container = await dbe.container.findUnique({ - select: {id: true, inventory: true, drink_id: true}, - where: {barcode: barcode} + const container = await dbe.container.findUnique({ + select: { + id: true, + inventory: true, + drink: { select: { name: true, image: true } }, + }, + where: { barcode: barcode }, }); // No drink found for barcode, do nothing @@ -48,58 +53,138 @@ export async function action({ // Log entry await dbe.history.create({data: {container: {connect: {id: container.id}}, inventoryAfter: newInventory}}); - return json({error: ""}); + return json({ + error: "", + lastScanned: { + name: container.drink.name, + image: container.drink.image ?? null, + }, + }); }; export default function ScanRoute() { const aData = useActionData(); const lData = useLoaderData(); + const [lastScanned, setLastScanned] = useState<{name:string;image:string|null} | null>(null); + const [showLastScanned, setShowLastScanned] = useState(false); + const inputRef = useRef(null); + const hasError = aData?.error ? aData.error.length > 0 : false; + const focusInput = () => { + window.setTimeout(() => inputRef.current?.focus(), 0); + }; + + useEffect(() => { + focusInput(); + }, []); + + useEffect(() => { + focusInput(); + }, [aData]); + + useEffect(() => { + const handleBlur = () => { + focusInput(); + }; + + const input = inputRef.current; + input?.addEventListener("blur", handleBlur); + + return () => { + input?.removeEventListener("blur", handleBlur); + }; + }, []); + + useEffect(() => { + if (!aData?.lastScanned) { + return; + } + + setLastScanned(aData.lastScanned); + setShowLastScanned(true); + + const timer = window.setTimeout(() => { + setShowLastScanned(false); + }, 5000); + + return () => window.clearTimeout(timer); + }, [aData?.lastScanned]); + return ( - - - - - - - Scan barcode: - - - - {aData?.error} - - - - - - - + + +

History

- - - - - - - - - - {lData.history.map((entry) => ( - - - - - - ))} - -
DrinkWhenAmount left
{entry.container.drink.name}{timeAgo(new Date(entry.checkoutAt))}{entry.inventoryAfter}
+
+ + + + + + + + + + {lData.history.map((entry) => ( + + + + + + ))} + +
DrinkWhenAmount left
{entry.container.drink.name}{timeAgo(new Date(entry.checkoutAt))}{entry.inventoryAfter}
+
+ + +

Scan Barcode

+ + +
+ + + + {aData?.error} + + +
+ +
+ + +
+ {showLastScanned && lastScanned ? ( + <> +

{lastScanned.name}

+ {lastScanned.image ? ( + {lastScanned.name} + ) : ( +
No image available
+ )} + + ) : ( +
+

Please scan your drink

+
+ )} +
+ +