diff --git a/package-lock.json b/package-lock.json
index 830866d..24f1cfd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,8 +13,10 @@
"@prisma/client": "^4.13.0",
"bootstrap": "^5.3.0-alpha3",
"bootstrap-icons": "^1.10.5",
+ "csv": "^6.2.11",
"eta": "^2.0.1",
"express": "^4.18.2",
+ "express-fileupload": "^1.4.0",
"jquery": "^3.6.4",
"lodash": "^4.17.21",
"prisma": "^4.13.0",
@@ -22,6 +24,7 @@
},
"devDependencies": {
"@types/express": "^4.17.17",
+ "@types/express-fileupload": "^1.4.1",
"@types/lodash": "^4.14.194",
"@types/signale": "^1.4.4",
"eslint": "^8.39.0",
@@ -249,6 +252,15 @@
"@types/node": "*"
}
},
+ "node_modules/@types/busboy": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.0.tgz",
+ "integrity": "sha512-ncOOhwmyFDW76c/Tuvv9MA9VGYUCn8blzyWmzYELcNGDb0WXWLSmFi7hJq25YdRBYJrmMBB5jZZwUjlJe9HCjQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@@ -270,6 +282,16 @@
"@types/serve-static": "*"
}
},
+ "node_modules/@types/express-fileupload": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@types/express-fileupload/-/express-fileupload-1.4.1.tgz",
+ "integrity": "sha512-sbl865h1Sser6SF+efpw2F/+roGISj+PRIbMcGXbtzgJQCBAeeBmoSo7sPge/mBa22ymCHfFPtHFsag/wUxwfg==",
+ "dev": true,
+ "dependencies": {
+ "@types/busboy": "*",
+ "@types/express": "*"
+ }
+ },
"node_modules/@types/express-serve-static-core": {
"version": "4.17.34",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz",
@@ -467,6 +489,17 @@
"concat-map": "0.0.1"
}
},
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -552,6 +585,35 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
+ "node_modules/csv": {
+ "version": "6.2.11",
+ "resolved": "https://registry.npmjs.org/csv/-/csv-6.2.11.tgz",
+ "integrity": "sha512-MZR5oNS0UvNne7nWsI+VaEI21RuZVrjfmhfihCalIz1n2jcst5h+mUCEgDWSR3sy5lVJhETp5g0s9SXWBLFH8w==",
+ "dependencies": {
+ "csv-generate": "^4.2.5",
+ "csv-parse": "^5.3.9",
+ "csv-stringify": "^6.3.3",
+ "stream-transform": "^3.2.5"
+ },
+ "engines": {
+ "node": ">= 0.1.90"
+ }
+ },
+ "node_modules/csv-generate": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.2.5.tgz",
+ "integrity": "sha512-CS4JQizdciP9dMGYz/wRm70+hbVGIYP+Hcx/kNR8eicAPZ3MUeq9G6AE8KqMZcZdcFv53m7gLpOaw2eg7nPP/w=="
+ },
+ "node_modules/csv-parse": {
+ "version": "5.3.9",
+ "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.3.9.tgz",
+ "integrity": "sha512-Nuh09OE1+wG6x5Lu2T+woxlupPAnWJ6Wj9XVYK74gP646e5gDrUsrCws1zz5NbckpQ+jygnxb8xDLj3gfBxi3w=="
+ },
+ "node_modules/csv-stringify": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.3.3.tgz",
+ "integrity": "sha512-bL926F/b6oJieWPt0njp3vfu8jySbPsDxCL4VUd9xHsJt3H/YJXLdzqOkh8y5l+1cBYOCEYbHNZ7ipVCfWzYSw=="
+ },
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -1015,6 +1077,17 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/express-fileupload": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz",
+ "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==",
+ "dependencies": {
+ "busboy": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2094,6 +2167,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/stream-transform": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.2.5.tgz",
+ "integrity": "sha512-B0p1gG7P0pFgVo4/xGACKC4IqcK+9DNCpkt+Jy/d9wVnbRxxJX1zQbJelBCsJ6H1mcfBt598T+JFP0DuA7NiHg=="
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
diff --git a/package.json b/package.json
index eca0116..f4e9b1d 100644
--- a/package.json
+++ b/package.json
@@ -21,8 +21,10 @@
"@prisma/client": "^4.13.0",
"bootstrap": "^5.3.0-alpha3",
"bootstrap-icons": "^1.10.5",
+ "csv": "^6.2.11",
"eta": "^2.0.1",
"express": "^4.18.2",
+ "express-fileupload": "^1.4.0",
"jquery": "^3.6.4",
"lodash": "^4.17.21",
"prisma": "^4.13.0",
@@ -30,6 +32,7 @@
},
"devDependencies": {
"@types/express": "^4.17.17",
+ "@types/express-fileupload": "^1.4.1",
"@types/lodash": "^4.14.194",
"@types/signale": "^1.4.4",
"eslint": "^8.39.0",
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index a9a4177..cf8a593 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -19,7 +19,7 @@ enum Status {
model Item {
id Int @id @default(autoincrement())
- SKU String @unique
+ SKU String? @unique
Amount Int
Comment String?
name String
@@ -31,6 +31,7 @@ model Item {
storageLocationId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
+ importedBy String?
}
model StorageLocation {
@@ -54,7 +55,7 @@ model StorageBuilding {
model Category {
id Int @id @default(autoincrement())
- name String
+ name String @unique
description String?
Item Item[]
}
diff --git a/src/frontend/demopage.eta.html b/src/frontend/demopage.eta.html
index 03ecb57..7d1a223 100644
--- a/src/frontend/demopage.eta.html
+++ b/src/frontend/demopage.eta.html
@@ -15,8 +15,8 @@
-
10
-
Uncategorized items
+
<%= it.stats.total %>
+
Items in total
@@ -44,27 +44,16 @@
-
- 1 |
- Samsung 65W USB-C Ladegerät |
- Normal |
- DB8031E8 |
- Edit |
-
-
- 2 |
- 2.5mm Klinken Verlängerung 40mm |
- Normal |
- 25A1E9DE |
- Edit |
-
-
- 3 |
- Elgato Streamdeck XL |
- Stolen |
- 9AF2388E |
- Edit |
-
+
+ <% it.recents.forEach(function(user){ %>
+
+ <%= user.id %> |
+ <%= user.name %> |
+ <%= user.status %> |
+ <%= user.SKU %> |
+ Edit |
+
+ <% }) %>
diff --git a/src/frontend/imports/csvImport.eta.html b/src/frontend/imports/csvImport.eta.html
new file mode 100644
index 0000000..82e58c5
--- /dev/null
+++ b/src/frontend/imports/csvImport.eta.html
@@ -0,0 +1,28 @@
+<%~ E.includeFile("../partials/head.eta.html", {"title": "Bootstrap Demo Page" }) %>
+ <%~ E.includeFile("../partials/controls.eta.html", {"active": "Orders" }) %>
+
+ Import A CSV File
+ Upload a CSV file to import into the database. The CSV file must have the following columns:
+
+ - Name
+ - Amount
+ - Manufacturer
+ - Category
+
+ The following columns are optional:
+
+ - SKU
+ - Comment
+ - StorageLocation (import currently not supported)
+
+
+
+
+ <%~ E.includeFile("../partials/controlsFoot.eta.html") %>
+ <%~ E.includeFile("../partials/foot.eta.html") %>
\ No newline at end of file
diff --git a/src/frontend/partials/controls.eta.html b/src/frontend/partials/controls.eta.html
index 0e9a137..7ff996b 100644
--- a/src/frontend/partials/controls.eta.html
+++ b/src/frontend/partials/controls.eta.html
@@ -10,14 +10,13 @@
aria-label="Toggle navigation">
-
+
-
+
diff --git a/src/frontend/partials/head.eta.html b/src/frontend/partials/head.eta.html
index 4e4e809..ce9578c 100644
--- a/src/frontend/partials/head.eta.html
+++ b/src/frontend/partials/head.eta.html
@@ -15,7 +15,7 @@
-
+
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index c790a70..ed18f3d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,7 @@
import { Signale } from 'signale';
import ConfigHandler from './assets/configHandler';
import express, { Request, Response } from 'express';
+import fileUpload from 'express-fileupload';
import { PrismaClient } from '@prisma/client';
import { Status, Category } from '@prisma/client';
import * as eta from 'eta';
@@ -43,6 +44,7 @@ export const prisma = new PrismaClient({
export const app = express();
app.engine("html", eta.renderFile)
+app.use(fileUpload());
// Configure static https://expressjs.com/de/starter/static-files.html
// app.use('/static', express.static('public'));
app.use(express.static(__path + '/static'));
diff --git a/src/routes/frontend/etaTest.ts b/src/routes/frontend/etaTest.ts
index c3eb0af..9d8f614 100644
--- a/src/routes/frontend/etaTest.ts
+++ b/src/routes/frontend/etaTest.ts
@@ -2,5 +2,17 @@ import express, { Request, Response } from 'express';
import { prisma, __path } from '../../index.js';
export default (req: Request, res: Response) => {
- res.render(__path + '/src/frontend/demopage.eta.html');
+ prisma.item.findMany({
+ orderBy: {
+ updatedAt: 'desc'
+ },
+ // Limit to 10 items
+ take: 10
+ }).then((items) => {
+ // Count amount of total items
+ prisma.item.count().then((count) => {
+ res.render(__path + '/src/frontend/demopage.eta.html', { recents: items, stats: { total: count } });
+ });
+ });
+ // res.render(__path + '/src/frontend/demopage.eta.html');
};
\ No newline at end of file
diff --git a/src/routes/frontend/import/csvImport.ts b/src/routes/frontend/import/csvImport.ts
new file mode 100644
index 0000000..8118577
--- /dev/null
+++ b/src/routes/frontend/import/csvImport.ts
@@ -0,0 +1,106 @@
+import express, { Request, Response } from 'express';
+import { prisma, __path, log } from '../../../index.js';
+import { UploadedFile } from 'express-fileupload';
+import { parse, transform } from 'csv';
+import { Status, Category, PrismaPromise } from '@prisma/client';
+
+export default (req: Request, res: Response) => {
+ // Decide wether its post or get
+ if (req.method === 'POST') {
+ // Handle file upload and import
+ console.log(req.files)
+ if (!req.files || Object.keys(req.files).length === 0) {
+ return res.status(400).send('No files were uploaded.');
+ }
+
+ const file: UploadedFile = req.files.formFile as UploadedFile;
+ const csv = file.data.toString();
+ parse(csv, { columns: true }, function (err, records) {
+ if (err) {
+ res.send(err);
+ return;
+ }
+ // Find all categories and save them into a set
+ const categories = new Set();
+ records.forEach((record: any) => {
+ categories.add(record.category);
+ });
+ // Remove categories that already exists in the database
+ prisma.category.findMany({
+ where: {
+ name: {
+ in: Array.from(categories)
+ }
+ }
+ }).then((values) => {
+ values.forEach((value) => {
+ categories.delete(value.name);
+ });
+ // Log categories
+ log.web.debug(categories);
+
+ const categoryPromises: PrismaPromise[] = [];
+ categories.forEach((category: string) => {
+ const promise = prisma.category.create({
+ data: {
+ name: category
+ }
+ })
+ categoryPromises.push(promise);
+ });
+ Promise.all(categoryPromises).then((values) => {
+ // Create items
+ const listOfPromises = [];
+
+ for (let i = 0; i < records.length; i++) {
+ const record = records[i];
+ const promise = prisma.item.create({
+ data: {
+ name: record.name,
+ Amount: parseInt(record.amount),
+ Comment: record.comment,
+ category: {
+ connect: {
+ name: record.category
+ }
+ },
+ SKU: record.sku,
+ manufacturer: record.manufacturer,
+ status: Status.normal,
+ importedBy: "CSV Import"
+ }
+ });
+ listOfPromises.push(promise);
+
+
+ }
+ Promise.all(listOfPromises).then((values) => {
+ console.log(values);
+ res.send('ok');
+ }).catch((err) => {
+ res.send('failed to create items');
+ log.db.error(err);
+ return;
+ });
+ }).catch((err) => {
+ // res.send('failed to create categories');
+ log.db.error(err);
+
+ });
+
+
+ }).catch((err) => {
+ res.send('failed to find categories');
+ log.db.error(err);
+ return;
+ });
+
+ });
+
+
+ } else {
+ // Render page
+ res.render(__path + '/src/frontend/imports/csvImport.eta.html');
+ }
+
+};
\ No newline at end of file
diff --git a/src/routes/frontend/index.ts b/src/routes/frontend/index.ts
index 489385c..f20cfe3 100644
--- a/src/routes/frontend/index.ts
+++ b/src/routes/frontend/index.ts
@@ -4,6 +4,7 @@ import express from 'express';
import skuRoute from './:id.js';
import testRoute from './test.js';
import etaTestRoute from './etaTest.js';
+import csvImportRoute from './import/csvImport.js';
// Router base is '/'
const Router = express.Router();
@@ -11,6 +12,7 @@ const Router = express.Router();
Router.use('/etaTest', etaTestRoute);
Router.use('/:id(\\w{8})', skuRoute);
Router.use('/test', testRoute);
+Router.use('/import/csv', csvImportRoute);
export default Router;
diff --git a/static/js/searchBox.js b/static/js/searchBox.js
new file mode 100644
index 0000000..4a821e4
--- /dev/null
+++ b/static/js/searchBox.js
@@ -0,0 +1,12 @@
+document.getElementById("SearchBox").addEventListener("keyup", handleSearchChange);
+function handleSearchChange(e) {
+ console.log(e.target.value);
+ // Check if known prefix is used (either > or #)
+ if (e.target.value[0] == ">") {
+ // Search for commands
+ } else if (e.target.value[0] == "#") {
+ // Search for SKU
+ } else {
+ // Search for name
+ }
+}
\ No newline at end of file