Graph colors, page titles and many more fixes

This commit is contained in:
2024-06-25 20:04:14 +02:00
parent b7d26e84cb
commit 4a2dfc6a84
18 changed files with 167 additions and 58 deletions
+18 -13
View File
@@ -110,19 +110,6 @@ const sliderIbu = () =>
))}
</Form.Group>
<Form.Group controlId="drink-modifiers" className='bg-body-secondary rounded p-3 mt-3'>
<Form.Label className='fw-bold'>Drink</Form.Label>
{ ["Sugar-free", "Gluten-free", "Organic"].map(key => (
<Form.Check
id={"drinkmod-" + key}
type="checkbox"
label={key}
name={key.toLowerCase()}
value={1}
defaultChecked={arg.searchParams.has(key.toLowerCase()) ? true : false}
/>
))}
</Form.Group>
<Form.Group controlId="drink-abv" className='bg-body-secondary rounded p-3 mt-3'>
<Form.Label className='fw-bold'>ABV</Form.Label>
{sliderAbv()}
@@ -214,6 +201,19 @@ const sliderIbu = () =>
/>
</Form.Group>
) : ""}
<Form.Group controlId="drink-modifiers" className='bg-body-secondary rounded p-3 mt-3'>
<Form.Label className='fw-bold'>Drink</Form.Label>
{ ["Sugar-free", "Gluten-free", "Organic"].map(key => (
<Form.Check
id={"drinkmod-" + key}
type="checkbox"
label={key}
name={key.toLowerCase()}
value={1}
defaultChecked={arg.searchParams.has(key.toLowerCase()) ? true : false}
/>
))}
</Form.Group>
<Form.Group controlId="inventory" className='bg-body-secondary rounded p-3 mt-3'>
<Form.Label className='fw-bold'>No Inventory</Form.Label>
<Form.Check
@@ -226,6 +226,11 @@ const sliderIbu = () =>
</Form.Group>
</Col>
</Row>
<Row>
<Col className='text-center p-3'>
<span className='fs-6 text-muted text-center'>Results are updated live</span>
</Col>
</Row>
</Form>
);
}
+9 -7
View File
@@ -1,5 +1,5 @@
import { enhance } from "@zenstackhq/runtime";
import { random_rgba } from "~/utils/color";
import { drinkTypeToColor, random_rgba } from "~/utils/color";
import timeAgo from "~/utils/datetime";
import { db } from "~/utils/db.server";
import { randomRange } from "~/utils/random";
@@ -25,6 +25,7 @@ export async function generateMonthChartData(){
// History of specific drink
const containersWithHistory = await dbe.container.findMany({
orderBy: [{drink: {type: "asc"}}, {checkouts: {_count: "desc"}}],
include: {drink: true, checkouts: {orderBy: {checkoutAt: "asc"}, where: {checkoutAt: {gte: dates[0]}}}}
})
@@ -86,14 +87,14 @@ export async function generateMonthChartData(){
inventoryAfter[i] = totalInventory;
}
let [color1] = random_rgba(1);
let color = drinkTypeToColor(container.drink.type);
datasets.push({
type: 'bar',
label: 'Checkouts ' + container.drink.name,
data: checkouts,
borderColor: color1,
backgroundColor: color1,
borderColor: color,
backgroundColor: color,
});
}
@@ -126,6 +127,7 @@ export async function generateMonthChartData(){
// History of specific drink
const containersWithHistory = await dbe.container.findMany({
orderBy: [{drink: {type: "asc"}}, {checkouts: {_count: "desc"}}],
include: {drink: true, checkouts: {orderBy: {checkoutAt: "asc"}, where: {checkoutAt: {gte: hours[0]}}}}
})
@@ -187,14 +189,14 @@ export async function generateMonthChartData(){
inventoryAfter[i] = totalInventory;
}
let [color1] = random_rgba(1);
let color = drinkTypeToColor(container.drink.type);
datasets.push({
type: 'bar',
label: 'Checkouts ' + container.drink.name,
data: checkouts,
borderColor: color1,
backgroundColor: color1,
borderColor: color,
backgroundColor: color,
});
}
+16 -13
View File
@@ -1,9 +1,8 @@
import { DrinkType } from "@prisma/client";
import { enhance } from "@zenstackhq/runtime";
import { hexToRGB, random_rgba } from "~/utils/color";
import timeAgo from "~/utils/datetime";
import { sortArrays } from "~/utils/arrays";
import { drinkTypeToColor, hexToRGB } from "~/utils/color";
import { db } from "~/utils/db.server";
import { randomRange } from "~/utils/random";
export async function generateTypeInventoryChartData(){
const dbe = enhance(db);
@@ -19,7 +18,7 @@ export async function generateTypeInventoryChartData(){
let typeT = DrinkType[type];
labels.push(type);
colors.push(random_rgba(1)[0]);
colors.push(drinkTypeToColor(typeT));
const aggregate = await dbe.container.aggregate({_sum: {inventory: true}, where: {drink: {type: typeT}}});
numset.push(aggregate._sum.inventory ?? 0);
@@ -56,7 +55,7 @@ export async function generateTypeInventoryChartData(){
let colors : string[] = [];
let numset : number[] = [];
const beerstyle = await dbe.beerStyle.findMany({});
const beerstyle = await dbe.beerStyle.findMany({orderBy: {beers: {_count: "desc"}}});
for(let style of beerstyle){
labels.push(style.name);
@@ -67,13 +66,15 @@ export async function generateTypeInventoryChartData(){
numset.push(reduced);
}
let [sortedNumset, sortedColors, sortedLabels] = sortArrays([numset, colors, labels]);
const data = {
labels,
labels: sortedLabels,
datasets: [{
type: 'doughnut',
label: 'Inventory',
data: numset,
backgroundColor: colors,
data: sortedNumset,
backgroundColor: sortedColors,
}]
};
@@ -93,7 +94,7 @@ export async function generateTypeInventoryChartData(){
let typeT = DrinkType[type];
labels.push(type);
colors.push(random_rgba(1)[0]);
colors.push(drinkTypeToColor(typeT));
const aggregate = await dbe.history.aggregate({_count: {_all: true}, where: {container: {drink: {type: typeT}}}});
numset.push(aggregate._count._all ?? 0);
@@ -130,7 +131,7 @@ export async function generateTypeInventoryChartData(){
let colors : string[] = [];
let numset : number[] = [];
const beerstyle = await dbe.beerStyle.findMany({});
const beerstyle = await dbe.beerStyle.findMany({orderBy: {beers: {_count: "desc"}}});
for(let style of beerstyle){
labels.push(style.name);
@@ -141,13 +142,15 @@ export async function generateTypeInventoryChartData(){
numset.push(reduced);
}
let [sortedNumset, sortedColors, sortedLabels] = sortArrays([numset, colors, labels]);
const data = {
labels,
labels: sortedLabels,
datasets: [{
type: 'doughnut',
label: 'Checked out',
data: numset,
backgroundColor: colors,
data: sortedNumset,
backgroundColor: sortedColors,
}]
};
+2 -1
View File
@@ -1,6 +1,7 @@
import type { LinksFunction } from "@remix-run/node";
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
@@ -29,7 +30,7 @@ function Document({
name="viewport"
content="width=device-width, initial-scale=1"
/>
<title>{title}</title>
<Meta />
<Links />
</head>
<body>
+5
View File
@@ -36,6 +36,11 @@ export default function EditContainerRoute() {
<Badge bg="secondary" className="ms-1">{timeAgo(new Date(checkout.checkoutAt))} - {checkout.checkoutAt.slice(0,-5)}</Badge>
<Badge bg="secondary" className="ms-1">{checkout.inventoryAfter} left</Badge>
<div className="float-end">
<LinkContainer to={"/admin/remove/checkout/" + checkout.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
<LinkContainer to={"" + checkout.id}>
<Button variant="primary" className="mx-3">
Edit
@@ -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.history.delete({where: {id: id}});
return redirect("/admin/edit/checkout");
}
+5 -1
View File
@@ -1,4 +1,4 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { LoaderFunctionArgs, MetaFunction, redirect } from "@remix-run/node";
import { Outlet, json, useLoaderData } from "@remix-run/react";
import { Container, Row } from "react-bootstrap";
import { getSession } from "~/auth/session";
@@ -30,6 +30,10 @@ export async function loader({
return json({data});
}
export const meta: MetaFunction = ({}) => {
return [{ title: "Admin | K-FRIDGE" }];
};
export default function InventoryLayout() {
let data = useLoaderData<typeof loader>();
+8 -1
View File
@@ -1,5 +1,6 @@
import type {
LoaderFunctionArgs
LoaderFunctionArgs,
MetaFunction
} from "@remix-run/node";
import { json } from "@remix-run/node";
import {
@@ -51,6 +52,12 @@ export default function BeerRoute() {
);
}
export const meta: MetaFunction<typeof loader> = ({
data,
}) => {
return [{ title: data?.beer.name + " | K-FRIDGE" }];
};
export function ErrorBoundary() {
const { beerid } = useParams();
const error = useRouteError();
@@ -1,5 +1,6 @@
import type {
LoaderFunctionArgs
LoaderFunctionArgs,
MetaFunction
} from "@remix-run/node";
import { json } from "@remix-run/node";
import {
@@ -50,6 +51,12 @@ return (
);
}
export const meta: MetaFunction<typeof loader> = ({
data,
}) => {
return [{ title: data?.cocktail.name + " | K-FRIDGE" }];
};
export function ErrorBoundary() {
const { cocktailid } = useParams();
const error = useRouteError();
+4 -1
View File
@@ -1,4 +1,4 @@
import { LoaderFunctionArgs } from "@remix-run/node";
import { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Outlet, json, useFetcher, useLoaderData, useRevalidator } from "@remix-run/react";
import { useEffect, useState } from "react";
import { getSession } from "~/auth/session";
@@ -23,6 +23,9 @@ export async function loader({
}
export const meta: MetaFunction = ({}) => {
return [{ title: "Inventory | K-FRIDGE" }];
};
export default function InventoryLayout() {
let data = useLoaderData<typeof loader>();
+1 -1
View File
@@ -50,7 +50,7 @@ export default function BeersRoute() {
return (
<Container>
<Row className="mt-3">
<Row className="pt-1 sticky-top bg-white" style={{boxShadow: "0px 4px 4px -5px rgba(0,0,0,.5)"}}>
<Col key="filtering" className="mb-3">
<div>
<Button variant="primary" onClick={handleShow}>Filters</Button>
+8 -1
View File
@@ -1,5 +1,6 @@
import type {
LoaderFunctionArgs
LoaderFunctionArgs,
MetaFunction
} from "@remix-run/node";
import { json } from "@remix-run/node";
import {
@@ -50,6 +51,12 @@ export default function SodaRoute() {
);
}
export const meta: MetaFunction<typeof loader> = ({
data,
}) => {
return [{ title: data?.soda.name + " | K-FRIDGE" }];
};
export function ErrorBoundary() {
const { sodaid } = useParams();
const error = useRouteError();
+22 -16
View File
@@ -14,6 +14,8 @@ import 'chart.js/auto';
import { db } from "~/utils/db.server";
import { generateBeerHistoryChartData, generateBeerInventoryChartData, generateTypeHistoryChartData, generateTypeInventoryChartData } from "~/models/type.chart.server";
import { drinkTypeToColor } from "~/utils/color";
import { DrinkType } from "@prisma/client";
function statsForContainers(containers : ContainerWithDrink[]){
var drinksInventory = 0;
@@ -119,6 +121,10 @@ export const loader = async ({request} : LoaderFunctionArgs) => {
return json({total, chartDataTotal, chartDataBeerTotal, statsLast24H, chartData24H, statsLastMonth, chartDataMonth, statsTotal, chartDataHistoryTotal, chartDataBeerHistoryTotal, history});
};
export const meta: MetaFunction = ({}) => {
return [{ title: "Stats | K-FRIDGE" }];
};
export default function StatsRoute() {
const loadData = useLoaderData<typeof loader>();
@@ -208,19 +214,19 @@ export default function StatsRoute() {
<td>{loadData.total.drinksInventory}</td>
</tr>
<tr>
<td className="ps-4"># Beers</td>
<td className="ps-4"># Beers <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Beer)}}> </Badge></td>
<td>{loadData.total.beersInventory}</td>
</tr>
<tr>
<td className="ps-4"># Wines</td>
<td className="ps-4"># Wines <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Wine)}}> </Badge></td>
<td>{loadData.total.wineInventory}</td>
</tr>
<tr>
<td className="ps-4"># Sodas</td>
<td className="ps-4"># Sodas <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Soda)}}> </Badge></td>
<td>{loadData.total.sodaInventory}</td>
</tr>
<tr>
<td className="ps-4"># Cocktails</td>
<td className="ps-4"># Cocktails <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Cocktail)}}> </Badge></td>
<td>{loadData.total.cocktailInventory}</td>
</tr>
</tbody>
@@ -273,19 +279,19 @@ export default function StatsRoute() {
<td>{loadData.statsTotal.drinksInventory}</td>
</tr>
<tr>
<td className="ps-4"># Beers</td>
<td className="ps-4"># Beers <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Beer)}}> </Badge></td>
<td>{loadData.statsTotal.beersInventory}</td>
</tr>
<tr>
<td className="ps-4"># Wines</td>
<td className="ps-4"># Wines <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Wine)}}> </Badge></td>
<td>{loadData.statsTotal.wineInventory}</td>
</tr>
<tr>
<td className="ps-4"># Sodas</td>
<td className="ps-4"># Sodas <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Soda)}}> </Badge></td>
<td>{loadData.statsTotal.sodaInventory}</td>
</tr>
<tr>
<td className="ps-4"># Cocktails</td>
<td className="ps-4"># Cocktails <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Cocktail)}}> </Badge></td>
<td>{loadData.statsTotal.cocktailInventory}</td>
</tr>
</tbody>
@@ -338,19 +344,19 @@ export default function StatsRoute() {
<td>{loadData.statsLastMonth.drinksInventory}</td>
</tr>
<tr>
<td className="ps-4"># Beers</td>
<td className="ps-4"># Beers <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Beer)}}> </Badge></td>
<td>{loadData.statsLastMonth.beersInventory}</td>
</tr>
<tr>
<td className="ps-4"># Wines</td>
<td className="ps-4"># Wines <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Wine)}}> </Badge></td>
<td>{loadData.statsLastMonth.wineInventory}</td>
</tr>
<tr>
<td className="ps-4"># Sodas</td>
<td className="ps-4"># Sodas <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Soda)}}> </Badge></td>
<td>{loadData.statsLastMonth.sodaInventory}</td>
</tr>
<tr>
<td className="ps-4"># Cocktails</td>
<td className="ps-4"># Cocktails <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Cocktail)}}> </Badge></td>
<td>{loadData.statsLastMonth.cocktailInventory}</td>
</tr>
</tbody>
@@ -394,19 +400,19 @@ export default function StatsRoute() {
<td>{loadData.statsLast24H.drinksInventory}</td>
</tr>
<tr>
<td className="ps-4"># Beers</td>
<td className="ps-4"># Beers <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Beer)}}> </Badge></td>
<td>{loadData.statsLast24H.beersInventory}</td>
</tr>
<tr>
<td className="ps-4"># Wines</td>
<td className="ps-4"># Wines <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Wine)}}> </Badge></td>
<td>{loadData.statsLast24H.wineInventory}</td>
</tr>
<tr>
<td className="ps-4"># Sodas</td>
<td className="ps-4"># Sodas <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Soda)}}> </Badge></td>
<td>{loadData.statsLast24H.sodaInventory}</td>
</tr>
<tr>
<td className="ps-4"># Cocktails</td>
<td className="ps-4"># Cocktails <Badge bg="" style={{backgroundColor: drinkTypeToColor(DrinkType.Cocktail)}}> </Badge></td>
<td>{loadData.statsLast24H.cocktailInventory}</td>
</tr>
</tbody>
+5 -1
View File
@@ -1,4 +1,4 @@
import { ActionFunctionArgs, EntryContext, LoaderFunctionArgs, json } from "@remix-run/node";
import { ActionFunctionArgs, EntryContext, LoaderFunctionArgs, MetaFunction, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Button, Col, Container, Form, Row, Table } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
@@ -44,6 +44,10 @@ export const loader = async ({request} : LoaderFunctionArgs) => {
return json({suggestions, isAdmin});
};
export const meta: MetaFunction = ({}) => {
return [{ title: "Suggestions | K-FRIDGE" }];
};
export default function SuggestionsRoute() {
const loadData = useLoaderData<typeof loader>();
+8 -1
View File
@@ -1,5 +1,6 @@
import type {
LoaderFunctionArgs
LoaderFunctionArgs,
MetaFunction
} from "@remix-run/node";
import { json } from "@remix-run/node";
import {
@@ -50,6 +51,12 @@ export default function WineRoute() {
);
}
export const meta: MetaFunction<typeof loader> = ({
data,
}) => {
return [{ title: data?.wine.name + " | K-FRIDGE" }];
};
export function ErrorBoundary() {
const { wineid } = useParams();
const error = useRouteError();
+16
View File
@@ -5,4 +5,20 @@ export function shuffleArray(array : any[]) {
array[i] = array[j];
array[j] = temp;
}
}
/**
* Sorts all arrays together with the first. Pass either a list of arrays, or a map. Any key is accepted.
* Array|Object arrays [sortableArray, ...otherArrays]; {sortableArray: [], secondaryArray: [], ...}
* Function comparator(?,?) -> int optional compareFunction, compatible with Array.sort(compareFunction)
*/
export function sortArrays(arrays : any[][], comparator = (a:any, b:any) => (a < b) ? 1 : (a > b) ? -1 : 0) {
let arrayKeys = Object.keys(arrays);
let sortableArray : (typeof arrays[0][0]) = Object.values(arrays)[0];
let indexes = Object.keys(sortableArray);
let sortedIndexes = indexes.sort((a, b) => comparator(sortableArray[a], sortableArray[b]));
let sortByIndexes = (array : any[], sortedIndexes : number[]) => sortedIndexes.map(sortedIndex => array[sortedIndex]);
return arrayKeys.map(arrayIndex => sortByIndexes(arrays[arrayIndex], sortedIndexes));
}
+16
View File
@@ -1,3 +1,5 @@
import { DrinkType } from "@prisma/client";
export function random_rgba(numShades : number) {
let o = Math.round, r = Math.random, s = 155, m = 100;
@@ -24,4 +26,18 @@ export function hexToRGB(hex : string, alpha : number) {
} else {
return "rgb(" + r + ", " + g + ", " + b + ")";
}
}
export function drinkTypeToColor(type: DrinkType){
switch(type){
default:
case DrinkType.Beer:
return 'rgba(255, 164, 18, 1)';
case DrinkType.Cocktail:
return 'rgba(83, 185, 219, 1)';
case DrinkType.Soda:
return 'rgba(188, 71, 214, 1)';
case DrinkType.Wine:
return 'rgba(178, 21, 58, 1)'
}
}
+1
View File
@@ -107,6 +107,7 @@ export default defineConfig({
route("edit/checkout", "routes/admin/edit/checkout.tsx");
route("edit/checkout/:id", "routes/admin/edit/checkout.$id.tsx");
route("remove/checkout/:id", "routes/admin/edit/remove/checkout.$id.tsx");
route("remove/checkouts/:containerid", "routes/admin/edit/remove/checkouts.$containerid.tsx");
route("remove/suggestion/:id", "routes/admin/edit/remove/suggestion.$id.tsx");