Add PDF report generating

This commit is contained in:
2024-06-28 20:46:38 +02:00
parent cabee4fd99
commit 638943d4c6
14 changed files with 444 additions and 15 deletions
+1
View File
@@ -5,6 +5,7 @@ export type ContainerWithDrink = Container & { drink: Drink}
export type ContainerWithSection = Container & { section: Section}
export type ContainerWithHistory = Container & { checkouts: History[]}
export type ContainerWithSectionAndHistory = ContainerWithSection & ContainerWithHistory
export type ContainerWithDrinkAndHistory = ContainerWithDrink & ContainerWithHistory
export type DrinkWithContainersAndSection = (Drink & { containers: ContainerWithSection[]})
export type DrinkWithContainersAndHistory = (Drink & { containers: ContainerWithHistory[]})
export type DrinkWithContainersAndSectionAndHistory = (Drink & { containers: ContainerWithSectionAndHistory[]})
@@ -0,0 +1,18 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { rmSync } from "node:fs";
import { enhance } from "~/utils/db.server";
export const loader = async ({
request,
params
} : LoaderFunctionArgs) => {
const { dbe } = await enhance(request);
const id = Number(params.id);
const report = await dbe.report.delete({where: {id: id}, select: {file: true}});
if(report.file) rmSync("public"+ report.file, {force: true});
return redirect("/admin/reports");
}
+79
View File
@@ -0,0 +1,79 @@
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
import { Button, Container, Form, Row } from "react-bootstrap";
import { enhance } from "~/utils/db.server";
import { createPdf } from "~/utils/pdf/pdf.generate.server";
import { slugify } from "~/utils/slugs";
export const action = async ({
request,
}: ActionFunctionArgs) => {
const { dbe } = await enhance(request);
const form = await request.formData();
const name = String(form.get("name")) || "";
const dateStart = String(form.get("dateStart")) + ":00.000z" || Date.now().toString();
const dateEnd = String(form.get("dateEnd")) + ":00.000z" || Date.now().toString();
try{
const lastIdEntries = await dbe.report.findMany({select: {id: true}, take: 1, orderBy:{createdAt: "desc"}})
const containersWithHistoryAndDrink = await dbe.container.findMany({
where: {checkouts: {some:{AND: [{checkoutAt: {gte: dateStart}}, {checkoutAt: {lte: dateEnd}}]}}},
include: {drink: true, checkouts: {where:{AND: [{checkoutAt: {gte: dateStart}}, {checkoutAt: {lte: dateEnd}}]}}}
});
const reportId = String(lastIdEntries[0] ? lastIdEntries[0].id + 1 : 0).padStart(8, "0");
const filepath = await createPdf(slugify(name), reportId, dateStart, dateEnd, containersWithHistoryAndDrink);
await dbe.report.create({ data: {
name: name,
dateStart: dateStart,
dateEnd: dateEnd,
file: filepath
}
});
}
catch(e){
console.log(e);
}
return redirect("/admin/reports");
};
export default function NewReportRoute() {
const startDate = new Date((Date.now() - (1000*60*60*24*30))).toISOString().slice(0,-8);
const endDate = new Date(Date.now()).toISOString().slice(0,-8);
return (
<Container>
<Row>
<h1>Create Report </h1>
<Form method="POST">
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control type="text" name="name" required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Date range start</Form.Label>
<Form.Control type="datetime-local" defaultValue={startDate} name="dateStart" required/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Date range end</Form.Label>
<Form.Control type="datetime-local" defaultValue={endDate} name="dateEnd" required/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Row>
</Container>
);
}
+1
View File
@@ -17,6 +17,7 @@ export default function AdminRoute() {
<ListGroup.Item><Link to="/admin/new/cocktail">cocktail</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/new/container">container</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/new/checkout">checkout</Link></ListGroup.Item>
<ListGroup.Item><Link to="/admin/new/report">report</Link></ListGroup.Item>
</ListGroup>
</div>
);
+47
View File
@@ -0,0 +1,47 @@
import { LoaderFunctionArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Button, Col, Container, ListGroup, Row } 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 reports = await dbe.report.findMany({orderBy: {createdAt: "desc"}});
return json({reports});
}
export default function ReportRoute() {
var loaderData = useLoaderData<typeof loader>();
return (
<Container>
<h1>Reports</h1>
<Row className="mt-5">
<Col>
<ListGroup>
{loaderData.reports.map((report) => (
<ListGroup.Item key={report.id}>
<b>{"#" + report.id.toString().padStart(8, "0")} {report.name}</b> ({report.dateStart.slice(0,-8).replace("T", " ")} - {report.dateEnd.slice(0,-8).replace("T", " ")})
<div className="float-end">
<LinkContainer to={"/admin/remove/report/" + report.id}>
<Button variant="danger">
Remove
</Button>
</LinkContainer>
<Button variant="primary" href={report.file} target="_blank" className="mx-3">
Download
</Button>
</div>
</ListGroup.Item>
))}
</ListGroup>
</Col>
</Row>
</Container>
);
}
+5
View File
@@ -11,6 +11,11 @@ export default function AdminRoute() {
<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>
<h1>Reports</h1>
<ListGroup>
<ListGroup.Item><Link to="/admin/reports">Reports</Link></ListGroup.Item>
</ListGroup>
</div>
);
}
+1 -1
View File
@@ -50,7 +50,7 @@ export default function BeersRoute() {
return (
<Container>
<Row className="pt-1 sticky-top bg-white" style={{boxShadow: "0px 4px 4px -5px rgba(0,0,0,.5)"}}>
<Row className="pt-1 sticky-top bg-white mb-2" 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>
+53
View File
@@ -0,0 +1,53 @@
import { jsPDF } from 'jspdf'
import {applyPlugin} from 'jspdf-autotable'
import { ContainerWithDrinkAndHistory, DrinkComposite, containerTypeToString } from '~/models/types';
import { volume } from '../conversions';
export async function createPdf(filename: string, reportId: string, dateStart: string, dateEnd: string, containers: ContainerWithDrinkAndHistory[]){
applyPlugin(jsPDF);
let body : string[][] = [];
let total : number = 0;
for(let container of containers){
let price = (container.price * container.checkouts.length);
total += price;
let bodyRow = [
container.drink.name + " - " + containerTypeToString(container.type) + " " + volume(container.volume),
"€" + container.price,
container.checkouts.length.toString(),
"€" + price];
body.push(bodyRow);
}
const doc = new jsPDF();
doc.setCreationDate(new Date(Date.now()));
doc.setFontSize(20);
doc.text("K-FRIDGE", 15, 25);
doc.setFontSize(12);
doc.text("Report #" + reportId, 160, 25);
doc.setFontSize(10);
doc.text("Selected date range: " + dateStart.slice(0, -8).replace("T", "") + " - " + dateEnd.slice(0,-8).replace("T", ""), 15, 35);
doc.autoTable({
startY: 40,
head: [['Drink', 'Price', 'Amount', 'Total']],
body: body,
foot: [["", "", "Total", "€" + total]]
});
let finalY = doc.autoTable.previous.finalY;
doc.text("Generated on " + new Date(Date.now()).toString(), 15, finalY + 10);
const filepath = "/reports/" + filename + ".pdf";
doc.save("public" + filepath);
return filepath;
}
+206 -13
View File
@@ -14,6 +14,8 @@
"@zenstackhq/runtime": "2.1.2",
"bootstrap": "^5.3.3",
"isbot": "^4.1.0",
"jspdf": "^2.5.1",
"jspdf-autotable": "^3.8.2",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
@@ -632,9 +634,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -2466,6 +2468,12 @@
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"node_modules/@types/react": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.0.tgz",
@@ -3252,6 +3260,17 @@
"node": ">=0.12.0"
}
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -3300,6 +3319,15 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"optional": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -3429,11 +3457,11 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -3480,6 +3508,17 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -3633,6 +3672,31 @@
}
]
},
"node_modules/canvg": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/canvg/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
},
"node_modules/capital-case": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz",
@@ -4045,6 +4109,17 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/core-js": {
"version": "3.37.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz",
"integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==",
"hasInstallScript": true,
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -4088,6 +4163,15 @@
"node": ">= 8"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -4395,6 +4479,12 @@
"csstype": "^3.0.2"
}
},
"node_modules/dompurify": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.5.tgz",
"integrity": "sha512-FgbqnEPiv5Vdtwt6Mxl7XSylttCC03cqP5ldNT2z+Kj0nLxPHJH4+1Cyf5Jasxhw93Rl4Oo11qRoUV72fmya2Q==",
"optional": true
},
"node_modules/dot-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
@@ -5591,6 +5681,11 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fflate": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -5604,9 +5699,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -6285,6 +6380,19 @@
"node": ">=12"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -7161,6 +7269,31 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jspdf": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
"integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
"dependencies": {
"@babel/runtime": "^7.14.0",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"fflate": "^0.4.8"
},
"optionalDependencies": {
"canvg": "^3.0.6",
"core-js": "^3.6.0",
"dompurify": "^2.2.0",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jspdf-autotable": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.2.tgz",
"integrity": "sha512-zW1ix99/mtR4MbIni7IqvrpfHmuTaICl6iv6wqjRN86Nxtwaw/QtOeDbpXqYSzHIJK9JvgtLM283sc5x+ipkJg==",
"peerDependencies": {
"jspdf": "^2.5.1"
}
},
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -9262,6 +9395,12 @@
"through2": "^2.0.3"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"node_modules/periscopic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
@@ -9838,6 +9977,15 @@
}
]
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -10385,6 +10533,15 @@
"node": ">=0.10.0"
}
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -10874,6 +11031,15 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -11213,6 +11379,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/tailwindcss": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
@@ -11383,6 +11558,15 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -12507,6 +12691,15 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@@ -13411,9 +13604,9 @@
"dev": true
},
"node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true,
"engines": {
"node": ">=8.3.0"
+2
View File
@@ -19,6 +19,8 @@
"@zenstackhq/runtime": "2.1.2",
"bootstrap": "^5.3.3",
"isbot": "^4.1.0",
"jspdf": "^2.5.1",
"jspdf-autotable": "^3.8.2",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
+10
View File
@@ -214,3 +214,13 @@ model Suggestion {
content String
resolved Boolean @default(false)
}
/// @@allow('all', auth().type == Admin)
model Report {
id Int @id() @default(autoincrement())
createdAt DateTime @default(now())
name String @unique()
dateStart DateTime
dateEnd DateTime
file String
}
+2 -1
View File
@@ -2,4 +2,5 @@ beers/*
wine/*
soda/*
cocktails/*
manufacturers/*
manufacturers/*
reports/*
+13
View File
@@ -231,4 +231,17 @@ model Suggestion {
resolved Boolean @default(false)
@@allow('all', true)
}
model Report {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
name String @unique
dateStart DateTime
dateEnd DateTime
file String
@@allow('all', auth().type == Admin)
}
+6
View File
@@ -45,6 +45,8 @@ export default defineConfig({
route("admin", "routes/admin/layout.tsx", () => {
route("", "routes/admin/route.tsx", { index: true });
route("reports", "routes/admin/reports.tsx");
route("new", "routes/admin/new/route.tsx");
route("new/country", "routes/admin/new/country.tsx");
route("new/section", "routes/admin/new/section.tsx");
@@ -57,6 +59,7 @@ export default defineConfig({
route("new/container", "routes/admin/new/container.tsx");
route("new/manufacturer","routes/admin/new/manufacturer.tsx");
route("new/checkout", "routes/admin/new/checkout.tsx");
route("new/report", "routes/admin/new/report.tsx");
route("edit", "routes/admin/edit/route.tsx");
@@ -113,6 +116,9 @@ export default defineConfig({
route("remove/suggestion/:id", "routes/admin/edit/remove/suggestion.$id.tsx");
route("resolve/suggestion/:id", "routes/admin/edit/suggestion.$id.tsx");
route("remove/report/:id", "routes/admin/edit/remove/report.$id.tsx");
route("manage/inventory", "routes/admin/manage/inventory.tsx");
route("manage/checkout", "routes/admin/manage/checkout.tsx");
});