Compare commits
29 Commits
fd7d1ffd47
...
master
Author | SHA1 | Date | |
---|---|---|---|
2eb80e0da9 | |||
dbcdce5296 | |||
7482c329ed | |||
aafaf4dd9e | |||
7b08d6e03f | |||
fe5cbabd46 | |||
141f75717b | |||
c38be00f73 | |||
ccbcb94449 | |||
86b9595665 | |||
c6e441dc26 | |||
c89eb37361 | |||
5cfd8b2319 | |||
d44900435f | |||
1e4ebc2a3c | |||
5ce521c8a7 | |||
ef16f045f7 | |||
bf561f8c7f | |||
5da8060857 | |||
b7d12d18d4 | |||
16ee092b35 | |||
366f3297da | |||
475690ca2b | |||
03fec1ebd7 | |||
8cd011fc01 | |||
9066397cd4 | |||
cf7bd8da9c | |||
14cf8af14b | |||
d491033c29 |
@ -33,7 +33,7 @@ model transactions {
|
|||||||
user user @relation(fields: [userId], references: [id])
|
user user @relation(fields: [userId], references: [id])
|
||||||
userId Int
|
userId Int
|
||||||
|
|
||||||
total Decimal @db.Decimal(5,2)
|
total Decimal @db.Decimal(8,2)
|
||||||
paid Boolean @default(false)
|
paid Boolean @default(false)
|
||||||
paidAt DateTime?
|
paidAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@ -55,7 +55,7 @@ model products {
|
|||||||
id Int @id @unique @default(autoincrement())
|
id Int @id @unique @default(autoincrement())
|
||||||
gtin String @unique // Dont try to use BigInt -> https://github.com/prisma/studio/issues/614
|
gtin String @unique // Dont try to use BigInt -> https://github.com/prisma/studio/issues/614
|
||||||
name String @unique
|
name String @unique
|
||||||
price Decimal @db.Decimal(5,2)
|
price Decimal @db.Decimal(8,2)
|
||||||
stock Int
|
stock Int
|
||||||
visible Boolean @default(true)
|
visible Boolean @default(true)
|
||||||
|
|
||||||
|
@ -1,17 +1,30 @@
|
|||||||
import log from './log.js';
|
|
||||||
import ConfigManager from '../libs/configManager.js';
|
import ConfigManager from '../libs/configManager.js';
|
||||||
import __path from './path.js';
|
import __path from './path.js';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Create a new config instance.
|
// Create a new config instance.
|
||||||
const config = new ConfigManager(__path + '/config.json', true, {
|
const config = new ConfigManager(__path + '/config.json', true, {
|
||||||
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
// db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
||||||
http_listen_address: '0.0.0.0',
|
http: {
|
||||||
http_port: 3000,
|
listen_address: '0.0.0.0',
|
||||||
http_domain: 'example.org',
|
port: 3000,
|
||||||
http_enable_hsts: false,
|
domain: 'example.org',
|
||||||
devmode: true,
|
enable_hsts: false,
|
||||||
devmode_fileupload: true
|
enable_csp: false
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
host: '',
|
||||||
|
port: 3306,
|
||||||
|
user: '',
|
||||||
|
password: '',
|
||||||
|
database: 'hydrationhub'
|
||||||
|
},
|
||||||
|
devmode: false,
|
||||||
|
devmode_fileupload: false,
|
||||||
|
galleryApiKey: '',
|
||||||
});//, log.core); // Disabled due to Cyclic dependencies with log handler (specifically-> devmode for loglevel)
|
});//, log.core); // Disabled due to Cyclic dependencies with log handler (specifically-> devmode for loglevel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
import { PrismaClient, Prisma } from '@prisma/client'; // Database
|
import { PrismaClient, Prisma } from '@prisma/client'; // Database
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
|
import __path from './path.js';
|
||||||
import log from './log.js';
|
import log from './log.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// Generate .env file for Prisma commands
|
||||||
|
const dotEnvPath = path.join(__path, '/.env')
|
||||||
|
const dotEnvExist = !fs.existsSync(dotEnvPath);
|
||||||
|
|
||||||
|
fs.writeFileSync(dotEnvPath, `DATABASE_URL="mysql://${config.global.mysql.user}:${config.global.mysql.password}@${config.global.mysql.host}:${config.global.mysql.port}/${config.global.mysql.database}"`);
|
||||||
|
log.core.info('Generated .env file for Prisma.');
|
||||||
|
if (dotEnvExist) {
|
||||||
|
log.db.error('Please run "npx prisma db push" to synchronize the database.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add errorhandling with some sort of message.
|
// TODO: Add errorhandling with some sort of message.
|
||||||
const prisma = new PrismaClient({
|
const prisma = new PrismaClient({
|
||||||
datasources: {
|
datasources: {
|
||||||
db: {
|
db: {
|
||||||
url: config.global.db_connection_string
|
url: `mysql://${config.global.mysql.user}:${config.global.mysql.password}@${config.global.mysql.host}:${config.global.mysql.port}/${config.global.mysql.database}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -44,3 +58,10 @@ export function handlePrismaError(errorObj: any, res: Response, source: string)
|
|||||||
export default prisma;
|
export default prisma;
|
||||||
|
|
||||||
//FIXME: https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/null-and-undefined
|
//FIXME: https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/null-and-undefined
|
||||||
|
|
||||||
|
|
||||||
|
// // Simulate a Prisma error for testing purposes
|
||||||
|
// throw new Prisma.PrismaClientKnownRequestError(
|
||||||
|
// 'Simulated Prisma error for testing',
|
||||||
|
// { code: 'P2000', clientVersion: 'unknown' } // Example error parameters
|
||||||
|
// );
|
||||||
|
10
src/index.ts
10
src/index.ts
@ -61,15 +61,15 @@ app.set('view engine', 'eta');
|
|||||||
|
|
||||||
// MARK: Express Middleware & Config
|
// MARK: Express Middleware & Config
|
||||||
app.set('x-powered-by', false); // helmet does this too. But not in devmode
|
app.set('x-powered-by', false); // helmet does this too. But not in devmode
|
||||||
if (!config.global.devmode) {
|
if (!config.global.devmode && config.global.http.enable_csp) {
|
||||||
app.use(
|
app.use(
|
||||||
helmet({
|
helmet({
|
||||||
strictTransportSecurity: config.global.http_enable_hsts,
|
strictTransportSecurity: config.global.http.enable_hsts,
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
useDefaults: false,
|
useDefaults: false,
|
||||||
directives: {
|
directives: {
|
||||||
defaultSrc: ["'self'"],
|
defaultSrc: ["'self'"],
|
||||||
scriptSrc: ["'self'", config.global.http_domain],
|
scriptSrc: ["'self'", config.global.http.domain],
|
||||||
objectSrc: ["'none'"],
|
objectSrc: ["'none'"],
|
||||||
upgradeInsecureRequests: config.global.devmode ? null : []
|
upgradeInsecureRequests: config.global.devmode ? null : []
|
||||||
}
|
}
|
||||||
@ -85,8 +85,8 @@ app.use(bodyParser.json());
|
|||||||
app.use(routes);
|
app.use(routes);
|
||||||
|
|
||||||
// TODO: Remove hardcoded http
|
// TODO: Remove hardcoded http
|
||||||
app.listen(config.global.http_port, config.global.http_listen_address, () => {
|
app.listen(config.global.http.port, config.global.http.listen_address, () => {
|
||||||
log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`);
|
log.web.info(`Listening at http://${config.global.http.listen_address}:${config.global.http.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
log.core.trace('Running from path: ' + __path);
|
log.core.trace('Running from path: ' + __path);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
import v1_routes from './v1/index.js';
|
import v1_router from './v1/index.js';
|
||||||
|
|
||||||
// Router base is '/api'
|
// Router base is '/api'
|
||||||
const Router = express.Router({ strict: false });
|
const Router = express.Router({ strict: false });
|
||||||
|
|
||||||
Router.use('/v1', v1_routes);
|
Router.use('/v1', v1_router);
|
||||||
|
|
||||||
export default Router;
|
export default Router;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
import testRoute from './test.js';
|
import test_route from './test.js';
|
||||||
import versionRoute from './version.js';
|
import version_route from './version.js';
|
||||||
|
|
||||||
import user_route from './user/user.js';
|
import user_route from './user/user.js';
|
||||||
import user_schema from './user/user_schema.js';
|
import user_schema from './user/user_schema.js';
|
||||||
@ -58,7 +58,7 @@ Router.route('/image/describe').get(image_schema);
|
|||||||
Router.route('/transaction').get(transaction_route.get).post(transaction_route.post).patch(transaction_route.patch).delete(transaction_route.del);
|
Router.route('/transaction').get(transaction_route.get).post(transaction_route.post).patch(transaction_route.patch).delete(transaction_route.del);
|
||||||
Router.route('/transaction/describe').get(transaction_schema);
|
Router.route('/transaction/describe').get(transaction_schema);
|
||||||
|
|
||||||
Router.route('/version').get(versionRoute.get);
|
Router.route('/version').get(version_route.get);
|
||||||
Router.route('/test').get(testRoute.get);
|
Router.route('/test').get(test_route.get);
|
||||||
|
|
||||||
export default Router;
|
export default Router;
|
||||||
|
@ -5,6 +5,13 @@ import { parseDynamicSortBy } from '../../../../helpers/prisma_helpers.js';
|
|||||||
import { schema_get, schema_post, schema_patch, schema_del } from './transaction_schema.js';
|
import { schema_get, schema_post, schema_patch, schema_del } from './transaction_schema.js';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
class AbortError extends Error {
|
||||||
|
constructor(public http_status: number, public status: string, public errorcode: string, public message: string, public details?: any) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'AbortError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: GET transaction
|
// MARK: GET transaction
|
||||||
async function get(req: Request, res: Response) {
|
async function get(req: Request, res: Response) {
|
||||||
const { error, value } = schema_get.validate(req.query);
|
const { error, value } = schema_get.validate(req.query);
|
||||||
@ -94,38 +101,81 @@ async function post(req: Request, res: Response) {
|
|||||||
log.api?.debug('POST transaction Success:', req.body, value);
|
log.api?.debug('POST transaction Success:', req.body, value);
|
||||||
|
|
||||||
const products: Array<number> = value.products;
|
const products: Array<number> = value.products;
|
||||||
let total = new Prisma.Decimal(0);
|
const outOfStockProducts: { id: number; name: string }[] = [];
|
||||||
|
const notFoundProducts: { id: number; name: string }[] = [];
|
||||||
const salesData: { productId: number; price: number }[] = [];
|
const salesData: { productId: number; price: number }[] = [];
|
||||||
|
let total = new Prisma.Decimal(0);
|
||||||
|
|
||||||
try {
|
|
||||||
// Start Prisma transaction
|
// Start Prisma transaction
|
||||||
await db.$transaction(async (prisma) => {
|
await db
|
||||||
|
.$transaction(async (prisma) => {
|
||||||
|
// Iterate over all products for this transaction(not prisma)
|
||||||
for (let i = 0; i < products.length; i++) {
|
for (let i = 0; i < products.length; i++) {
|
||||||
log.api?.debug('Product:', products[i]);
|
log.api?.debug('Product:', i + 1, 'of', products.length, '(Loop)');
|
||||||
|
|
||||||
|
// Get product (price, stock, name)
|
||||||
const product = await prisma.products.findUnique({
|
const product = await prisma.products.findUnique({
|
||||||
where: { id: products[i] },
|
where: { id: products[i] },
|
||||||
select: { price: true }
|
select: { price: true, stock: true, name: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if product exists
|
||||||
if (product) {
|
if (product) {
|
||||||
log.api?.debug('Price:', product.price, Number(product.price));
|
log.api?.debug('Price:', product.price, '[Name:' + product.name + ']', '[ID:' + products[i] + ']');
|
||||||
//total += Number(product.price);
|
|
||||||
|
if (product.stock > 0) {
|
||||||
|
// Add price of current product to total
|
||||||
total = total.add(product.price);
|
total = total.add(product.price);
|
||||||
|
|
||||||
|
// Add product to salesData -> Later generate sales entry for each product
|
||||||
salesData.push({
|
salesData.push({
|
||||||
productId: products[i],
|
productId: products[i],
|
||||||
price: Number(product.price)
|
price: Number(product.price)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reduce stock by 1
|
||||||
|
await prisma.products.update({
|
||||||
|
where: { id: products[i] },
|
||||||
|
data: { stock: { decrement: 1 } }
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
log.api?.debug('Product not found:', products[i]);
|
// Product is out of stock
|
||||||
|
outOfStockProducts.push({ id: products[i], name: product.name });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Product not found
|
||||||
|
notFoundProducts.push({ id: products[i], name: 'unknown' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.api?.debug('Total:', total.toFixed(2));
|
log.api?.debug('Total:', total.toFixed(2));
|
||||||
|
|
||||||
// TODO: Check if user exists
|
// Abort the Prisma transaction if there are not existing products
|
||||||
|
if (notFoundProducts.length > 0) {
|
||||||
|
log.api?.debug('Aborting. missing products:', notFoundProducts);
|
||||||
|
|
||||||
// Create transaction with sales
|
throw new AbortError(
|
||||||
|
400, // http_status
|
||||||
|
'ERROR', // status
|
||||||
|
'NOT_FOUND', // errorcode
|
||||||
|
'Some of the products included in the transaction do not exist, therefore the transaction has not been processed.', // message
|
||||||
|
notFoundProducts // details
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort the Prisma transaction if there are products with insufficient stock
|
||||||
|
if (outOfStockProducts.length > 0) {
|
||||||
|
log.api?.debug('Aborting. out of stock products:', outOfStockProducts);
|
||||||
|
throw new AbortError(
|
||||||
|
400, // http_status
|
||||||
|
'ERROR', // status
|
||||||
|
'OUT_OF_STOCK', // errorcode
|
||||||
|
'Some of the products included in the transaction are out of stock, therefore the transaction has not been processed.', // message
|
||||||
|
outOfStockProducts // details
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create transaction with salesData
|
||||||
const transaction = await prisma.transactions.create({
|
const transaction = await prisma.transactions.create({
|
||||||
data: {
|
data: {
|
||||||
userId: value.user_id,
|
userId: value.user_id,
|
||||||
@ -140,11 +190,21 @@ async function post(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Everything went well
|
||||||
res.status(201).json({ status: 'CREATED', message: 'Successfully created transaction', id: transaction.id });
|
res.status(201).json({ status: 'CREATED', message: 'Successfully created transaction', id: transaction.id });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err instanceof AbortError) {
|
||||||
|
res.status(err.http_status).json({
|
||||||
|
status: err.status,
|
||||||
|
errorcode: err.errorcode,
|
||||||
|
message: err.message,
|
||||||
|
details: err.details
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} else {
|
||||||
handlePrismaError(err, res, 'POST transaction');
|
handlePrismaError(err, res, 'POST transaction');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +223,8 @@ async function patch(req: Request, res: Response) {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
userId: value.user_id,
|
userId: value.user_id,
|
||||||
paid: value.paid
|
paid: value.paid,
|
||||||
|
paidAt: value.paid ? new Date() : undefined
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true
|
id: true
|
||||||
|
@ -30,6 +30,7 @@ const schema_post = validator.object({
|
|||||||
|
|
||||||
// MARK: UPDATE transaction
|
// MARK: UPDATE transaction
|
||||||
const schema_patch = validator.object({
|
const schema_patch = validator.object({
|
||||||
|
id: validator.number().positive().precision(0).required(),
|
||||||
user_id: validator.number().positive().precision(0).required(),
|
user_id: validator.number().positive().precision(0).required(),
|
||||||
paid: validator.boolean().default(false)
|
paid: validator.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
import dashboard_Route from './dashboard.js';
|
import dashboard_route from './dashboard.js';
|
||||||
import users from './users.js';
|
import users_route from './users.js';
|
||||||
import products from './products.js';
|
import products_route from './products.js';
|
||||||
|
import report_route from './report.js';
|
||||||
|
|
||||||
// Router base is '/admin'
|
// Router base is '/admin'
|
||||||
const Router = express.Router({ strict: false });
|
const Router = express.Router({ strict: false });
|
||||||
|
|
||||||
Router.route('/').get(dashboard_Route.get);
|
Router.route('/').get(dashboard_route.get);
|
||||||
Router.route('/users').get(users.get);
|
Router.route('/users').get(users_route.get);
|
||||||
Router.route('/products').get(products.get);
|
Router.route('/products').get(products_route.get);
|
||||||
// Router.route('/user_select').get(user_select_Route.get);
|
Router.route('/report').get(report_route.get);
|
||||||
// Router.route('/product_select').get(product_select_Route.get);
|
|
||||||
// Router.route('/pay_up').get(pay_up_Route.get);
|
|
||||||
|
|
||||||
export default Router;
|
export default Router;
|
||||||
|
7
src/routes/frontend/admin/report.ts
Normal file
7
src/routes/frontend/admin/report.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
|
||||||
|
function get(req: Request, res: Response) {
|
||||||
|
res.render("admin/reports")
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { get };
|
@ -2,10 +2,10 @@ import express from 'express';
|
|||||||
import config from '../../handlers/config.js';
|
import config from '../../handlers/config.js';
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
import screensaver_Route from './screensaver.js';
|
import screensaver_route from './screensaver.js';
|
||||||
import user_select_Route from './user_select.js';
|
import user_select_route from './user_select.js';
|
||||||
import product_select_Route from './product_select.js';
|
import product_select_route from './product_select.js';
|
||||||
import pay_up_Route from './pay_up.js';
|
import pay_up_route from './pay_up.js';
|
||||||
import test_Route from './test.js';
|
import test_Route from './test.js';
|
||||||
|
|
||||||
import adminRouter from './admin/index.js';
|
import adminRouter from './admin/index.js';
|
||||||
@ -13,10 +13,10 @@ import adminRouter from './admin/index.js';
|
|||||||
// Router base is '/'
|
// Router base is '/'
|
||||||
const Router = express.Router({ strict: false });
|
const Router = express.Router({ strict: false });
|
||||||
|
|
||||||
Router.route('/').get(screensaver_Route.get);
|
Router.route('/').get(screensaver_route.get);
|
||||||
Router.route('/user_select').get(user_select_Route.get);
|
Router.route('/user_select').get(user_select_route.get);
|
||||||
Router.route('/product_select').get(product_select_Route.get);
|
Router.route('/product_select').get(product_select_route.get);
|
||||||
Router.route('/pay_up').get(pay_up_Route.get);
|
Router.route('/pay_up').get(pay_up_route.get);
|
||||||
|
|
||||||
Router.use('/admin', adminRouter);
|
Router.use('/admin', adminRouter);
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import express, { Request, Response } from 'express';
|
import express, { Request, Response } from 'express';
|
||||||
|
import config from '../../handlers/config.js';
|
||||||
|
|
||||||
function get(req: Request, res: Response) {
|
function get(req: Request, res: Response) {
|
||||||
res.render("screensaver")
|
res.render("screensaver", { apikey: config.global.galleryApiKey })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { get };
|
export default { get };
|
||||||
|
@ -4,8 +4,8 @@ import __path from '../handlers/path.js';
|
|||||||
import log from '../handlers/log.js';
|
import log from '../handlers/log.js';
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
import frontend_routes from './frontend/index.js';
|
import frontend_router from './frontend/index.js';
|
||||||
import api_routes from './api/index.js';
|
import api_router from './api/index.js';
|
||||||
|
|
||||||
const Router = express.Router({ strict: false });
|
const Router = express.Router({ strict: false });
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ Router.use('/libs/jquery', express.static(path.join(__path, 'node_modules', 'jqu
|
|||||||
Router.use('/libs/bootstrap-icons', express.static(path.join(__path, 'node_modules', 'bootstrap-icons')));
|
Router.use('/libs/bootstrap-icons', express.static(path.join(__path, 'node_modules', 'bootstrap-icons')));
|
||||||
|
|
||||||
// Other routers
|
// Other routers
|
||||||
Router.use('/api', api_routes);
|
Router.use('/api', api_router);
|
||||||
Router.use('/', frontend_routes);
|
Router.use('/', frontend_router);
|
||||||
|
|
||||||
// Default route.
|
// Default route.
|
||||||
Router.all('*', function (req, res) {
|
Router.all('*', function (req, res) {
|
||||||
|
@ -100,13 +100,13 @@ let _api = {
|
|||||||
// Handle the response
|
// Handle the response
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
_testPageFail(response.statusText);
|
_testPageFail(response.statusText);
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
// Handle the result, was json valid?
|
// Handle the result, was json valid?
|
||||||
if (!result) {
|
if (!result) {
|
||||||
_testPageFail('Invalid JSON response');
|
_testPageFail('Invalid JSON response');
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -172,7 +172,7 @@ function getApiDescriptionByTable(tableName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnTableDataByTableName(tableName, search="", orderBy="asc", sort="", take=-1, skip=0) {
|
function returnTableDataByTableName(tableName, search="", orderBy="asc", sort="", take=-1, skip=0, filters=[]) {
|
||||||
var orderBy = orderBy.toLowerCase();
|
var orderBy = orderBy.toLowerCase();
|
||||||
if(orderBy == "") {
|
if(orderBy == "") {
|
||||||
orderBy = "asc";
|
orderBy = "asc";
|
||||||
@ -187,6 +187,10 @@ function returnTableDataByTableName(tableName, search="", orderBy="asc", sort=""
|
|||||||
if(skip > 0) {
|
if(skip > 0) {
|
||||||
baseString += "&skip=" + skip;
|
baseString += "&skip=" + skip;
|
||||||
}
|
}
|
||||||
|
filterKeys = Object.keys(filters);
|
||||||
|
for(var i = 0; i < filterKeys.length; i++) {
|
||||||
|
baseString += "&" + filterKeys[i] + "=" + filters[filterKeys[i]];
|
||||||
|
}
|
||||||
|
|
||||||
if (search && search.length > 0) {
|
if (search && search.length > 0) {
|
||||||
return _api.get(baseString + '&search=' + search);
|
return _api.get(baseString + '&search=' + search);
|
||||||
@ -214,6 +218,7 @@ async function getCountByTable(tableName, search="") {
|
|||||||
|
|
||||||
|
|
||||||
function _testPageFail(reason) {
|
function _testPageFail(reason) {
|
||||||
|
return;
|
||||||
document.getElementById('heroStatus').classList.remove('is-success');
|
document.getElementById('heroStatus').classList.remove('is-success');
|
||||||
document.getElementById('heroStatus').classList.add('is-danger');
|
document.getElementById('heroStatus').classList.add('is-danger');
|
||||||
|
|
||||||
|
@ -4,23 +4,23 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
bottom: 10%;
|
bottom: 10%;
|
||||||
right: 5%;
|
right: 5%; /* Verschiebt die Flexbox weiter nach links */
|
||||||
z-index: 900010;
|
z-index: 900010;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
width: 40%;
|
||||||
width: 30%;
|
margin: 0 auto; /* Stellt sicher, dass die Box zentriert bleibt */
|
||||||
}
|
}
|
||||||
|
|
||||||
#time {
|
#time {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 95%;
|
width: 140%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
@ -47,3 +47,13 @@ flex-wrap: wrap;
|
|||||||
95%,
|
95%,
|
||||||
100% {background-position:0 75%,0 25%,100% 75%,100% 25%}
|
100% {background-position:0 75%,0 25%,100% 75%,100% 25%}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#credits {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1px;
|
||||||
|
left: 1px;
|
||||||
|
mix-blend-mode: difference;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
13
static/js/kiosk_mode.js
Normal file
13
static/js/kiosk_mode.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// TODO: How to start kiosk mode?
|
||||||
|
if (true) {
|
||||||
|
console.info('Kiosk mode -> Disabled all external links');
|
||||||
|
document.querySelectorAll('a').forEach((link) => {
|
||||||
|
if (link.classList.contains('external-link')) {
|
||||||
|
link.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
@ -1,8 +1,4 @@
|
|||||||
// Image Handler
|
// Image Handler
|
||||||
const baseUrl = 'https://api.unsplash.com/photos/random?client_id=[KEY]&orientation=landscape&topics=nature';
|
|
||||||
const apiKey = 'tYOt7Jo94U7dunVcP5gt-kDKDMjWFOGQNsHuhLDLV8k'; // Take from config
|
|
||||||
const fullUrl = baseUrl.replace('[KEY]', apiKey);
|
|
||||||
|
|
||||||
const showModeImage = '/static/media/showModeLockscreen.jpg';
|
const showModeImage = '/static/media/showModeLockscreen.jpg';
|
||||||
|
|
||||||
let credits = document.getElementById('credits');
|
let credits = document.getElementById('credits');
|
||||||
@ -16,9 +12,27 @@ document.body.addEventListener('click', () => {
|
|||||||
// Lock screen or show mode
|
// Lock screen or show mode
|
||||||
let screenState = 'lock';
|
let screenState = 'lock';
|
||||||
|
|
||||||
|
let cookieScreen = getCookie('screen');
|
||||||
|
if (cookieScreen) {
|
||||||
|
screenState = cookieScreen;
|
||||||
|
}
|
||||||
|
|
||||||
function handleImage() {
|
function handleImage() {
|
||||||
if (screenState === 'lock') {
|
if (screenState === 'lock') {
|
||||||
fetch('https://staging.thegreydiamond.de/projects/photoPortfolio/api/getRand.php?uuid=01919dec-b2cd-7adc-8ca2-a071d1169cbc&unsplash=true&orientation=landscape')
|
const apiParams = {
|
||||||
|
// default galery; spring awakens
|
||||||
|
uuid: '01919dec-b2cd-7adc-8ca2-a071d1169cbc;01953de0-3aa7-71f1-bfff-cbf9488efa64',
|
||||||
|
unsplash: true,
|
||||||
|
orientation: 'landscape',
|
||||||
|
height: window.screen.availHeight,
|
||||||
|
width: window.screen.availWidth,
|
||||||
|
cropCenteringMode: 'sm',
|
||||||
|
apikey: apiKey
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiUrl = `https://photo.thegreydiamond.de/api/images/random.php?${new URLSearchParams(apiParams).toString()}`;
|
||||||
|
|
||||||
|
fetch(apiUrl)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// data = {
|
// data = {
|
||||||
@ -66,7 +80,7 @@ function handleImage() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Set the credits
|
// Set the credits
|
||||||
credits.innerHTML = `Photo by <a href="${data.user.links.html}" target="_blank">${data.user.name}</a> on <a href="https://unsplash.com" target="_blank">Unsplash</a>`;
|
credits.innerHTML = `"${data.title}" by <a href="${data.user.links.html}" class="external-link" target="_blank">${data.user.name}</a>`;
|
||||||
credits.style.zIndex = 300000;
|
credits.style.zIndex = 300000;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
@ -13,3 +20,8 @@ hidden {
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 1rem !important;
|
||||||
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 MiB After Width: | Height: | Size: 1.5 MiB |
@ -74,6 +74,9 @@ tables.forEach(async (table) => {
|
|||||||
refreshTable(table);
|
refreshTable(table);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if(table.getAttribute("data-loadmode") == "manual") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
refreshTable(table);
|
refreshTable(table);
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -240,6 +243,7 @@ modalForms.forEach((modalForm) => {
|
|||||||
console.log('Response: ', resp);
|
console.log('Response: ', resp);
|
||||||
if (resp['status'] == 'CREATED' || resp['status'] == 'UPDATED') {
|
if (resp['status'] == 'CREATED' || resp['status'] == 'UPDATED') {
|
||||||
console.log('Entry created successfully');
|
console.log('Entry created successfully');
|
||||||
|
createTemporaryNotification('Eintrag erfolgreich aktualisiert', 'is-success');
|
||||||
modalForm.closest('.modal').classList.remove('is-active');
|
modalForm.closest('.modal').classList.remove('is-active');
|
||||||
modalForm.reset();
|
modalForm.reset();
|
||||||
// Hide loadPhase
|
// Hide loadPhase
|
||||||
@ -260,12 +264,18 @@ modalForms.forEach((modalForm) => {
|
|||||||
entryPhase.classList.remove('is-hidden');
|
entryPhase.classList.remove('is-hidden');
|
||||||
}
|
}
|
||||||
// TODO: Show error message
|
// TODO: Show error message
|
||||||
|
createTemporaryNotification('Error while creating entry', 'is-danger');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all tables with data-searchTargetId set to table
|
// Find all tables with data-searchTargetId set to table
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if(modalForm.getAttribute('data-extTable') != null) {
|
||||||
refreshTableByName(table);
|
refreshTableByName(table);
|
||||||
updateSingeltonsByTableName(table);
|
updateSingeltonsByTableName(table);
|
||||||
|
} else {
|
||||||
|
refreshTableByName(document.getElementById(modalForm.getAttribute('data-extTable')));
|
||||||
|
}
|
||||||
|
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -282,6 +292,7 @@ async function refreshTable(table) {
|
|||||||
});
|
});
|
||||||
let order = '';
|
let order = '';
|
||||||
let column = '';
|
let column = '';
|
||||||
|
let filters = JSON.parse(table.getAttribute('data-filters')) || {};
|
||||||
ths.forEach((th) => {
|
ths.forEach((th) => {
|
||||||
if (th.hasAttribute('data-order')) {
|
if (th.hasAttribute('data-order')) {
|
||||||
order = th.getAttribute('data-order');
|
order = th.getAttribute('data-order');
|
||||||
@ -310,7 +321,7 @@ async function refreshTable(table) {
|
|||||||
if (searchField) {
|
if (searchField) {
|
||||||
const value = searchField.value;
|
const value = searchField.value;
|
||||||
const dbTable = table.getAttribute('data-dataSource');
|
const dbTable = table.getAttribute('data-dataSource');
|
||||||
const result = await returnTableDataByTableName(dbTable, value, order, column, take=maxLinesPerPage, skip= start);
|
const result = await returnTableDataByTableName(dbTable, value, order, column, take=maxLinesPerPage, skip=start, filters);
|
||||||
const totalResultCount = await getCountByTable(dbTable, value);
|
const totalResultCount = await getCountByTable(dbTable, value);
|
||||||
paginationPassOnPre['dataLength'] = totalResultCount;
|
paginationPassOnPre['dataLength'] = totalResultCount;
|
||||||
var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
|
var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
|
||||||
@ -319,7 +330,7 @@ async function refreshTable(table) {
|
|||||||
clearTable(table);
|
clearTable(table);
|
||||||
writeDataToTable(table, data, paginationPassOn);
|
writeDataToTable(table, data, paginationPassOn);
|
||||||
} else {
|
} else {
|
||||||
const result = await returnTableDataByTableName(table.getAttribute('data-dataSource'), undefined, order, column, take= maxLinesPerPage, skip= start);
|
const result = await returnTableDataByTableName(table.getAttribute('data-dataSource'), undefined, order, column, take= maxLinesPerPage, skip= start, filters);
|
||||||
const resultCount = await getCountByTable(table.getAttribute('data-dataSource'));
|
const resultCount = await getCountByTable(table.getAttribute('data-dataSource'));
|
||||||
paginationPassOnPre['dataLength'] = resultCount;
|
paginationPassOnPre['dataLength'] = resultCount;
|
||||||
var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
|
var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
|
||||||
@ -504,7 +515,14 @@ function writeDataToTable(table, data, paginationPassOn) {
|
|||||||
if(header.getAttribute('data-type') == "bool") {
|
if(header.getAttribute('data-type') == "bool") {
|
||||||
td.innerHTML = row[column] ? '<i class="bi bi-check"></i>' : '<i class="bi bi-x"></i>';
|
td.innerHTML = row[column] ? '<i class="bi bi-check"></i>' : '<i class="bi bi-x"></i>';
|
||||||
|
|
||||||
|
} else if(header.getAttribute('data-type') == "datetime"){
|
||||||
|
if(row[column] == null) {
|
||||||
|
td.innerHTML = "";
|
||||||
} else {
|
} else {
|
||||||
|
td.innerHTML = formatTimestamp(row[column]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
td.innerHTML = row[column];
|
td.innerHTML = row[column];
|
||||||
}
|
}
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
@ -681,3 +699,40 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setCookie(name, value, days) {
|
||||||
|
let expires = "";
|
||||||
|
if(days) {
|
||||||
|
let date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + value + expires + "; path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
let value = "; " + document.cookie;
|
||||||
|
let parts = value.split("; " + name + "=");
|
||||||
|
if(parts.length == 2) {
|
||||||
|
return parts.pop().split(";").shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function eraseCookie(name) {
|
||||||
|
document.cookie = name + '=; Max-Age=-99999999;';
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorIfAnyUndefined(inp) {
|
||||||
|
console.log(inp)
|
||||||
|
for(var i = 0; i < inp.length; i++) {
|
||||||
|
if(inp[i] == undefined) {
|
||||||
|
console.error("Missing element!")
|
||||||
|
createTemporaryNotification("Beim Laden der Seite ist ein Fehler aufgetreten", "is-danger", 90000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestamp(timestamp) {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
@ -1,6 +1,26 @@
|
|||||||
let uploadFileInput = document.getElementById('imgUpload');
|
let uploadFileInput = document.getElementById('imgUpload');
|
||||||
let fileName = document.getElementById('fileName');
|
let fileName = document.getElementById('fileName');
|
||||||
let imgUploadForm = document.getElementById('imgUploadForm');
|
let imgUploadForm = document.getElementById('imgUploadForm');
|
||||||
|
let scannerField = document.getElementById('scannerField');
|
||||||
|
let btn_restock = document.getElementById('btn_restock');
|
||||||
|
|
||||||
|
let btn_save_2 = document.getElementById('btn_save_2');
|
||||||
|
|
||||||
|
let form_gtin = document.getElementById('form_gtin');
|
||||||
|
|
||||||
|
let modal_stage_1 = document.getElementById('modal-stage-1');
|
||||||
|
let modal_stage_2 = document.getElementById('modal-stage-2');
|
||||||
|
let modal_stage_3 = document.getElementById('modal-stage-3');
|
||||||
|
|
||||||
|
let modal_stage_2_result = document.getElementById("stage-2-result");
|
||||||
|
|
||||||
|
let modal_stage_2_amount = document.getElementById("stage-2-amount");
|
||||||
|
|
||||||
|
let globalData;
|
||||||
|
|
||||||
|
waitingForScan = false;
|
||||||
|
let currentRestockProduct = null;
|
||||||
|
|
||||||
|
|
||||||
function handleImagePresence(row) {
|
function handleImagePresence(row) {
|
||||||
// Check if /api/v1/image?id=row&check returns true
|
// Check if /api/v1/image?id=row&check returns true
|
||||||
@ -21,7 +41,7 @@ function handleImagePresence(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pretty = isThere ? '<i class="bi bi-check"></i>' : '<i class="bi bi-x"></i></a>';
|
let pretty = isThere ? '<i class="bi bi-check"></i>' : '<i class="bi bi-x"></i></a>';
|
||||||
const template = `<a href="/api/v1/image?id=${row.id}" target="_blank">${pretty}</a> <i class="bi bi-dot"></i> <button class="btn btn-primary" onclick="uploadImage(${row.id})">Upload</button>`;
|
const template = `<a href="/api/v1/image?id=${row.id}" target="_blank">${pretty}</a> <i class="bi bi-dot"></i> <button class="btn btn-primary" onclick="uploadImage(${row.id})"><i class="bi bi-upload"></i></button>`;
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +75,10 @@ function silentFormSubmit() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enableScanner() {
|
||||||
|
waitingForScan = true;
|
||||||
|
scannerField.focus();
|
||||||
|
}
|
||||||
|
|
||||||
uploadFileInput.addEventListener('change', function() {
|
uploadFileInput.addEventListener('change', function() {
|
||||||
fileName.innerHTML = this.files[0].name;
|
fileName.innerHTML = this.files[0].name;
|
||||||
@ -63,3 +87,109 @@ uploadFileInput.addEventListener('change', function() {
|
|||||||
refreshTableByName('products');
|
refreshTableByName('products');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
scannerField.style.fontSize = '1px';
|
||||||
|
scannerField.style.height = '1px';
|
||||||
|
scannerField.style.width = '1px';
|
||||||
|
scannerField.style.opacity = '0';
|
||||||
|
scannerField.style.position = 'relative';
|
||||||
|
|
||||||
|
// Make sure text fields is always centerd vertically
|
||||||
|
window.addEventListener('scroll', function(event) {
|
||||||
|
if(!waitingForScan) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scannerField.y = document.documentElement.scrollTop + 20;
|
||||||
|
scannerField.style.top = document.documentElement.scrollTop + 20 + "px";
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if(!waitingForScan) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scannerField.focus();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
btn_restock.addEventListener('click', function() {
|
||||||
|
modal_stage_1.classList.remove('is-hidden');
|
||||||
|
modal_stage_2.classList.add('is-hidden');
|
||||||
|
modal_stage_3.classList.add('is-hidden');
|
||||||
|
waitingForScan = true;
|
||||||
|
});
|
||||||
|
// Handle barcode scanner input
|
||||||
|
scannerField.addEventListener('keydown', async function(event) {
|
||||||
|
if(event.key != 'Enter') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let barcode = scannerField.value;
|
||||||
|
console.log('Barcode scanned:', barcode);
|
||||||
|
scannerField.value = "";
|
||||||
|
// createTemporaryNotification(`Barcode ${barcode} gescannt`, 'is-info');
|
||||||
|
waitingForScan = false;
|
||||||
|
|
||||||
|
// Check if barcode is in the database
|
||||||
|
let product = globalData.find(p => p.gtin == barcode);
|
||||||
|
if(product) {
|
||||||
|
console.log('Product found:', product);
|
||||||
|
currentRestockProduct = product;
|
||||||
|
modal_stage_2_amount.innerHTML = "Aktuelle Menge: " + product.stock;
|
||||||
|
modal_stage_1.classList.add('is-hidden');
|
||||||
|
modal_stage_2.classList.remove('is-hidden');
|
||||||
|
modal_stage_3.classList.add('is-hidden');
|
||||||
|
createTemporaryNotification(`<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} gefunden`, 'is-success');
|
||||||
|
} else {
|
||||||
|
modal_stage_1.classList.add('is-hidden');
|
||||||
|
modal_stage_2.classList.add('is-hidden');
|
||||||
|
modal_stage_3.classList.remove('is-hidden');
|
||||||
|
form_gtin.value = barcode;
|
||||||
|
}
|
||||||
|
// modal_stage_2_result.innerHTML = product ? `<i class="bi bi-check"></i> Produkt gefunden: ${product.name}` : `<i class="bi bi-x"></i> Produkt nicht gefunden`;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// let product = globalData.find(p => p.gtin == barcode);
|
||||||
|
// if(product) {
|
||||||
|
// let event = new Event('click');
|
||||||
|
// createTemporaryNotification(`<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} gefunden`, 'is-success');
|
||||||
|
// document.getElementById(`product_${product.id}`).dispatchEvent(event);
|
||||||
|
// } else {
|
||||||
|
// createTemporaryNotification( `<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} nicht gefunden`, 'is-danger');
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
function restock(amount) {
|
||||||
|
currentRestockProduct.stock += amount;
|
||||||
|
modal_stage_2_amount.innerHTML = "Aktuelle Menge: " + currentRestockProduct.stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStock() {
|
||||||
|
let result = _api.patch('products', {
|
||||||
|
"id": currentRestockProduct.id,
|
||||||
|
"stock": currentRestockProduct.stock
|
||||||
|
})
|
||||||
|
if(result) {
|
||||||
|
createTemporaryNotification('Bestand erfolgreich aktualisiert', 'is-success');
|
||||||
|
modal_stage_2.classList.add('is-hidden');
|
||||||
|
modal_stage_1.classList.remove('is-hidden');
|
||||||
|
modal_stage_3.classList.add('is-hidden');
|
||||||
|
enableScanner();
|
||||||
|
} else {
|
||||||
|
createTemporaryNotification('Fehler beim Aktualisieren des Bestands', 'is-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
|
let data = await returnTableDataByTableName('products');
|
||||||
|
console.info(`Found ${data.count} products`);
|
||||||
|
const result = data.result;
|
||||||
|
globalData = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// btn_save_2.addEventListener('click', async function() {
|
||||||
|
// // Assume submission is valid
|
||||||
|
// // Get the form data
|
||||||
|
// // reload table
|
||||||
|
// // close modal
|
||||||
|
// });
|
@ -1,4 +1,82 @@
|
|||||||
const tableContent = document.querySelector('.table-content');
|
const tableContent = document.querySelector('.table-content');
|
||||||
const tableSum = document.querySelector('.table-sum');
|
// HTML Elements
|
||||||
|
const isEmptyAlert = document.getElementById("noBalance");
|
||||||
|
const tableDiv = document.getElementById("balanceSheet");
|
||||||
|
const payTable = document.getElementById("payTable");
|
||||||
|
const tableCnt = document.getElementById("table-content");
|
||||||
|
const tableSum = document.getElementById("table-sum");
|
||||||
|
const modal_sum = document.getElementById("ModalSum");
|
||||||
|
const confirmModal = document.getElementById("confirmModal");
|
||||||
|
const btn_paynow = document.getElementById("paynow");
|
||||||
|
const btn_confirm = document.getElementById("confirmCheckout");
|
||||||
|
const btn_logout = document.getElementById("logout");
|
||||||
|
|
||||||
alert("NYI: Endpoint is not yet implemented. This demo ends here.");
|
const table_old = document.getElementById("alltransactions");
|
||||||
|
|
||||||
|
errorIfAnyUndefined([isEmptyAlert, tableDiv, payTable, tableCnt, tableSum, modal_sum])
|
||||||
|
|
||||||
|
// Current user
|
||||||
|
let cookieUser = getCookie('user');
|
||||||
|
|
||||||
|
if(cookieUser == undefined) {
|
||||||
|
createTemporaryNotification('Fehler: Nutzer nicht angemeldet.', 'is-danger');
|
||||||
|
window.location.href = '/user_select';
|
||||||
|
}
|
||||||
|
table_old.setAttribute('data-filters', `{"user_id": ${cookieUser}}`);
|
||||||
|
refreshTable(table_old);
|
||||||
|
console.log("Table refreshed");
|
||||||
|
|
||||||
|
let transactionIds = [];
|
||||||
|
|
||||||
|
// Request outstanding transactions by user
|
||||||
|
async function pullData() {
|
||||||
|
let data = await _api.get("transaction?user_id=" + parseInt(cookieUser) + "&paid=false");
|
||||||
|
console.log(data)
|
||||||
|
if(data.count == 0) {
|
||||||
|
isEmptyAlert.classList.remove("is-hidden");
|
||||||
|
tableDiv.classList.add("is-hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Write data to table
|
||||||
|
const result = data.result;
|
||||||
|
let priceSum = 0;
|
||||||
|
for(var i = 0; i < data.count; i++) {
|
||||||
|
const row = result[i];
|
||||||
|
const newRow = tableCnt.insertRow();
|
||||||
|
newRow.id = `row_${row.id}`;
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td>${formatTimestamp(row.createdAt)}</td>
|
||||||
|
<td>${parseFloat(row.total).toFixed(2)} €</td>
|
||||||
|
`;
|
||||||
|
priceSum += parseFloat(row.total);
|
||||||
|
transactionIds.push(row.id);
|
||||||
|
}
|
||||||
|
tableSum.innerText = priceSum.toFixed(2) + " €";
|
||||||
|
modal_sum.innerText = priceSum.toFixed(2) + " €";
|
||||||
|
}
|
||||||
|
|
||||||
|
btn_paynow.onclick = () => {
|
||||||
|
confirmModal.classList.add("is-active");
|
||||||
|
}
|
||||||
|
|
||||||
|
btn_confirm.onclick = () => {
|
||||||
|
for(let i = 0; i < transactionIds.length; i++) {
|
||||||
|
let res = _api.patch(`transaction`, {paid: true, id: transactionIds[i], user_id: parseInt(cookieUser)});
|
||||||
|
console.log(res);
|
||||||
|
if(res == -1 || res == undefined) {
|
||||||
|
createTemporaryNotification('Fehler: Zahlung fehlgeschlagen.', 'is-danger');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createTemporaryNotification('Zahlung erfolgreich.', 'is-success');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/user_select';
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
btn_logout.onclick = () => {
|
||||||
|
eraseCookie('user');
|
||||||
|
window.location.href = '/user_select';
|
||||||
|
}
|
||||||
|
|
||||||
|
pullData()
|
||||||
|
@ -186,13 +186,7 @@ function confirmedCart() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
let value = "; " + document.cookie;
|
|
||||||
let parts = value.split("; " + name + "=");
|
|
||||||
if(parts.length == 2) {
|
|
||||||
return parts.pop().split(";").shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle barcode scanner
|
// Handle barcode scanner
|
||||||
// Force the cursor to the scanner field
|
// Force the cursor to the scanner field
|
||||||
@ -222,7 +216,7 @@ scannerField.addEventListener('keydown', async function(event) {
|
|||||||
let product = globalData.find(p => p.gtin == barcode);
|
let product = globalData.find(p => p.gtin == barcode);
|
||||||
if(product) {
|
if(product) {
|
||||||
let event = new Event('click');
|
let event = new Event('click');
|
||||||
createTemporaryNotification(`<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} gefunden`, 'is-success');
|
createTemporaryNotification(`<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} gefunden`, 'is-success', 2000);
|
||||||
document.getElementById(`product_${product.id}`).dispatchEvent(event);
|
document.getElementById(`product_${product.id}`).dispatchEvent(event);
|
||||||
} else {
|
} else {
|
||||||
createTemporaryNotification( `<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} nicht gefunden`, 'is-danger');
|
createTemporaryNotification( `<i class="bi bi-upc-scan"></i> Barcode scan: GTIN ${barcode} nicht gefunden`, 'is-danger');
|
||||||
|
@ -12,8 +12,22 @@ let pinInput4 = document.getElementById('pinInput4');
|
|||||||
|
|
||||||
let pinError = document.getElementById('pinError');
|
let pinError = document.getElementById('pinError');
|
||||||
|
|
||||||
|
let lastActivity = new Date().getTime();
|
||||||
|
let lastActivityTimeout = 1000 * 60 * 5; // 5 minutes
|
||||||
|
|
||||||
let currentUser = null;
|
let currentUser = null;
|
||||||
|
|
||||||
|
document.addEventListener('click', function() {
|
||||||
|
lastActivity = new Date().getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
let now = new Date().getTime();
|
||||||
|
if(now - lastActivity > lastActivityTimeout) {
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
// Attach event listeners to all numpad buttons
|
// Attach event listeners to all numpad buttons
|
||||||
let numpadButtons = numpad.getElementsByTagName('button');
|
let numpadButtons = numpad.getElementsByTagName('button');
|
||||||
for(let i = 0; i < numpadButtons.length; i++) {
|
for(let i = 0; i < numpadButtons.length; i++) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<a href="/admin/users" class="button is-large is-fullwidth is-primary">Benutzer</a>
|
<a href="/admin/users" class="button is-large is-fullwidth is-primary">Benutzer</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-4">
|
<div class="column is-4">
|
||||||
<a href="/admin/reports" class="button is-large is-fullwidth is-primary">Berichte</a>
|
<a href="/admin/report" class="button is-large is-fullwidth is-primary">Berichte</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
<%~ include("partials/base_head.eta", {"title": "Admin - Benutzer"}) %>
|
<%~ include("partials/base_head.eta", {"title": "Admin - Benutzer"}) %>
|
||||||
<%~ include("partials/nav.eta") %>
|
<%~ include("partials/nav.eta") %>
|
||||||
|
<input id="scannerField" type="text"/>
|
||||||
<section class="section container" id="mainSelect">
|
<section class="section container" id="mainSelect">
|
||||||
<h1 class="title">Produktverwaltung</h1>
|
<h1 class="title">Produktverwaltung</h1>
|
||||||
<p class="heading"><button class="js-modal-trigger button" data-target="modal-js-example">
|
<p class="heading buttons">
|
||||||
|
<button class="js-modal-trigger button" data-target="modal-js-example">
|
||||||
Neues Produkt anlegen
|
Neues Produkt anlegen
|
||||||
</button></p>
|
</button><button class="js-modal-trigger button" data-target="modal-restock" id="btn_restock">
|
||||||
|
Lager nachfüllen / Anpassen
|
||||||
|
</button><br></p>
|
||||||
|
|
||||||
<input class="input" type="text" data-searchTargetId="productTable" placeholder="Nach Produkt suchen.." />
|
<input class="input" type="text" data-searchTargetId="productTable" placeholder="Nach Produkt suchen.." />
|
||||||
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="products" id="productTable" data-pageSize="10">
|
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="products" id="productTable" data-pageSize="10">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th data-dataCol = "id">Id</th>
|
<th data-dataCol = "id">ID</th>
|
||||||
<th data-dataCol = "name">Name</th>
|
<th data-dataCol = "name">Name</th>
|
||||||
<th data-dataCol = "gtin">GTIN</th>
|
<th data-dataCol = "gtin">GTIN</th>
|
||||||
<th data-dataCol = "price">Preis</th>
|
<th data-dataCol = "price">Preis</th>
|
||||||
<th data-dataCol = "stock">Lagermenge</th>
|
<th data-dataCol = "stock">Lagermenge</th>
|
||||||
<th data-dataCol = "visible" data-type="bool">Sichtbarkeit</th>
|
<th data-dataCol = "visible" data-type="bool">Sichtbarkeit</th>
|
||||||
<th data-dataCol = "FUNC:INLINE" data-ColHandler=handleImagePresence>Bild hinterlegt</th>
|
<th data-dataCol = "FUNC:INLINE" data-ColHandler=handleImagePresence>Bild</th>
|
||||||
<th data-fnc="actions" data-actions="edit,delete">Aktionen</th>
|
<th data-fnc="actions" data-actions="edit,delete">Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -148,6 +151,86 @@
|
|||||||
<button class="modal-close is-large" aria-label="close"></button>
|
<button class="modal-close is-large" aria-label="close"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-restock" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="box" id="modal-stage-1">
|
||||||
|
<h2 class="title">Nachfüllen</h1>
|
||||||
|
<center><h1 class="title"><i class="bi bi-upc-scan"></i></h1></center>
|
||||||
|
Warten auf Scan....
|
||||||
|
</div>
|
||||||
|
<div class="box" id="modal-stage-2">
|
||||||
|
<h2 class="title">Scan erfolgreich - Produktmenge eingeben</h1>
|
||||||
|
<h3 class="subtitle" id="stage-2-amount">Aktuelle Menge: 0</h3>
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="button is-info" onclick="restock(-1)">-1</button>
|
||||||
|
<button class="button is-info" onclick="restock(1)">+1</button>
|
||||||
|
<button class="button is-info" onclick="restock(6)">+6</button>
|
||||||
|
<button class="button is-info" onclick="restock(10)">+10</button>
|
||||||
|
<button class="button is-info" onclick="restock(12)">+12</button>
|
||||||
|
</div>
|
||||||
|
<button class="button is-success" onclick="applyStock()">Änderungen speichern</button>
|
||||||
|
<div id="stage-2-result"></div>
|
||||||
|
</div>
|
||||||
|
<div class="box" id="modal-stage-3">
|
||||||
|
<h2 class="title">Scan erfolgreich - Produkt erstellen</h1>
|
||||||
|
<form data-targetTable="products">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Bezeichner</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="text" placeholder="Schokolade" value="" name="name">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-file-earmark-person-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">GTIN</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input id="form_gtin" class="input" type="number" placeholder="" value="" name="gtin" readonly>
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-upc"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Lagermenge</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="number" placeholder="" value="" name="stock">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-archive-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Preis</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="number" placeholder="" value="" step=0.01 name="price">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-currency-euro"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<input type="submit" class="button is-link" value="Save" data-actionBtn="save" data-extTable="productTable" id="btn_save_2">
|
||||||
|
</div>
|
||||||
|
<!--<div class="control">
|
||||||
|
<button type="button" class="button is-link is-light" data-actionBtn="cancel">Cancel</button>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="modal-close is-large" aria-label="close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/static/pages/admin_products.js"></script>
|
<script src="/static/pages/admin_products.js"></script>
|
||||||
<%~ include("partials/footer.eta") %>
|
<%~ include("partials/footer.eta") %>
|
||||||
<%~ include("partials/base_foot.eta") %>
|
<%~ include("partials/base_foot.eta") %>
|
||||||
|
36
views/admin/reports.eta
Normal file
36
views/admin/reports.eta
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<%~ include("partials/base_head.eta", {"title": "Dashboard"}) %>
|
||||||
|
<%~ include("partials/nav.eta") %>
|
||||||
|
|
||||||
|
<section class="section container" id="mainSelect">
|
||||||
|
<h1 class="title">Berichte</h1>
|
||||||
|
<!-- Big buttons linking to the different admin pages (Produkte, Benutzer, Bericht) -->
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Benutzer</p>
|
||||||
|
<p class="title"><span data-dataSource="user" data-dataAction="COUNT" class="is-skeleton">Load.</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Transaktionen</p>
|
||||||
|
<p class="title"><span data-dataSource="transaction" data-dataAction="COUNT" class="is-skeleton">Load.</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Produkte</p>
|
||||||
|
<p class="title"><span data-dataSource="products" data-dataAction="COUNT" class="is-skeleton">Load.</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="columns is-centered">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<%~ include("partials/footer.eta") %>
|
||||||
|
<!-- <script src="/static/pages/admin_.js"></script>-->
|
||||||
|
<%~ include("partials/base_foot.eta") %>
|
@ -4,7 +4,7 @@
|
|||||||
<section class="section container" id="mainSelect">
|
<section class="section container" id="mainSelect">
|
||||||
<h1 class="title">Benutzerverwaltung</h1>
|
<h1 class="title">Benutzerverwaltung</h1>
|
||||||
<p class="heading"><button class="js-modal-trigger button" data-target="modal-js-example">
|
<p class="heading"><button class="js-modal-trigger button" data-target="modal-js-example">
|
||||||
Neuen Konakt anlegen
|
Benutzer anlegen
|
||||||
</button></p>
|
</button></p>
|
||||||
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="user" id="userTable" data-pageSize="10">
|
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="user" id="userTable" data-pageSize="10">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
<div class="content has-text-centered">
|
<div class="content has-text-centered">
|
||||||
<p>
|
<p>
|
||||||
<i class="bi bi-cup-straw"></i>
|
<i class="bi bi-cup-straw"></i>
|
||||||
<strong>HydrationHUB</strong> by <a target="_blank" rel="noopener noreferrer" href="https://pnh.fyi">[Project-name-here]</a>.<br>
|
<strong>HydrationHUB</strong> by <a target="_blank" rel="noopener noreferrer" href="https://pnh.fyi" class="external-link">[Project-name-here]</a>.<br>
|
||||||
Running Version <span data-dataSource="version" data-dataAction="SPECIAL" class="is-skeleton">Load.</span>
|
Running Version <span data-dataSource="version" data-dataAction="SPECIAL" class="is-skeleton">Load.</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="/static/apiWrapper.js"></script>
|
<script src="/static/apiWrapper.js"></script>
|
||||||
<script src="/static/pageDriver.js"></script>
|
<script src="/static/pageDriver.js"></script>
|
||||||
|
<script src="/static/js/kiosk_mode.js"></script>
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item primary" href="/">
|
<a class="navbar-item primary" href="/">
|
||||||
<i class="bi bi-cup-straw"></i>
|
<i class="bi bi-cup-straw"></i>
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item primary is-hidden" id="nav_username" href="/">
|
||||||
|
<strong>Hey, <span id="nav_usernameContent"></span></strong>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
||||||
@ -15,61 +16,83 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="navbarBasicExample" class="navbar-menu">
|
<div id="navbarBasicExample" class="navbar-menu">
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item" href="/">Screensaver</a>
|
|
||||||
<a class="navbar-item" href="/user_select">user_select</a>
|
|
||||||
<a class="navbar-item" href="/product_select">product_select</a>
|
|
||||||
<a class="navbar-item" href="/test">Test <span class="tag is-info">Dev</span></a>
|
|
||||||
|
|
||||||
<!--<div class="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a class="navbar-link">More</a>
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<a class="navbar-item">About</a>
|
|
||||||
<a class="navbar-item is-selected">Jobs</a>
|
|
||||||
<a class="navbar-item">Contact</a>
|
|
||||||
<hr class="navbar-divider">
|
|
||||||
<a class="navbar-item">Report an issue</a>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item is-hidden" id="showOnLogin">
|
<div class="navbar-item" id="dynamic-navbar-buttons">
|
||||||
<strong>Hey, <span id="nav_username"></span></strong>
|
<!-- Buttons will be dynamically injected here -->
|
||||||
<button class="button" onclick="window.location='/pay_up'" >Zur Abrechnung</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-item is-hidden" id="onlyShowRoot">
|
|
||||||
<button class="button" onclick="window.location='/admin/'" >Zur Administration</button>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-item is-hidden" id="onlyShowAdmin">
|
|
||||||
<button class="button" onclick="window.location='/admin/'" >Zur Administration</button>
|
|
||||||
<button class="button" onclick="window.location='/'" >Abmelden</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
// Check if ?user is set
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (window.location.search.includes('user')) {
|
const navbarButtons = document.getElementById('dynamic-navbar-buttons');
|
||||||
// Show the sign up button
|
const currentPath = window.location.pathname;
|
||||||
document.querySelector('#showOnLogin').classList.remove('is-hidden');
|
const queryParams = new URLSearchParams(window.location.search);
|
||||||
// Get the username from the cookie
|
|
||||||
username = document.cookie.split('; ').find(row => row.startsWith('name')).split('=')[1];
|
const buttonsConfig = {
|
||||||
// Set the username in the nav
|
'/user_select': [
|
||||||
document.getElementById('nav_username').innerText = username;
|
{ text: '', icon: 'bi bi-gear', link: '/admin' },
|
||||||
|
{ text: '', icon: 'bi bi-house', link: '/user_select' }
|
||||||
|
],
|
||||||
|
'/product_select': [
|
||||||
|
{ text: 'Zur Abrechnung', link: '/pay_up' },
|
||||||
|
{ text: '', icon: 'bi bi-gear', link: '/admin' },
|
||||||
|
{ text: '', icon: 'bi bi-box-arrow-right', link: '/user_select' }
|
||||||
|
],
|
||||||
|
'/pay_up': [
|
||||||
|
{ text: '', icon: 'bi bi-gear', link: '/admin' },
|
||||||
|
{ text: '', icon: 'bi bi-box-arrow-right', link: '/user_select' }
|
||||||
|
],
|
||||||
|
'/admin': [
|
||||||
|
{ text: '', icon: 'bi bi-house', link: '/user_select' }
|
||||||
|
],
|
||||||
|
'/admin/products': [
|
||||||
|
{ text: '', icon: 'bi bi-arrow-return-left', link: '/admin' },
|
||||||
|
{ text: '', icon: 'bi bi-house', link: '/user_select' }
|
||||||
|
],
|
||||||
|
'/admin/users': [
|
||||||
|
{ text: '', icon: 'bi bi-arrow-return-left', link: '/admin' },
|
||||||
|
{ text: '', icon: 'bi bi-house', link: '/user_select' }
|
||||||
|
],
|
||||||
|
'/admin/report': [
|
||||||
|
{ text: '', icon: 'bi bi-arrow-return-left', link: '/admin' },
|
||||||
|
{ text: '', icon: 'bi bi-house', link: '/user_select' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentPath === '/product_select' && queryParams.has('user')) {
|
||||||
|
const username = document.cookie.split('; ').find(row => row.startsWith('name'))?.split('=')[1];
|
||||||
|
if (username) {
|
||||||
|
document.getElementById('nav_usernameContent').innerText = username; // Set greeting
|
||||||
|
document.getElementById('nav_username').classList.remove('is-hidden'); // Show greeting
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if /user_select is the current page
|
const buttons = buttonsConfig[currentPath] || [];
|
||||||
if (window.location.pathname == '/user_select') {
|
buttons.forEach(button => {
|
||||||
// Show the sign up button
|
const btn = document.createElement('button');
|
||||||
document.querySelector('#onlyShowRoot').classList.remove('is-hidden');
|
btn.className = 'button';
|
||||||
|
btn.onclick = () => window.location = button.link;
|
||||||
|
if (button.icon) {
|
||||||
|
const icon = document.createElement('i');
|
||||||
|
icon.className = button.icon;
|
||||||
|
btn.appendChild(icon);
|
||||||
}
|
}
|
||||||
|
if (button.text) {
|
||||||
// If admin is contained in url
|
btn.appendChild(document.createTextNode(button.text));
|
||||||
if (window.location.pathname.includes('admin')) {
|
|
||||||
// Show the sign up button
|
|
||||||
document.querySelector('#onlyShowAdmin').classList.remove('is-hidden');
|
|
||||||
}
|
}
|
||||||
|
navbarButtons.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Burger menu toggle
|
||||||
|
const burger = document.querySelector('.navbar-burger');
|
||||||
|
const menu = document.querySelector('.navbar-menu');
|
||||||
|
|
||||||
|
if (burger && menu) {
|
||||||
|
burger.addEventListener('click', () => {
|
||||||
|
burger.classList.toggle('is-active');
|
||||||
|
menu.classList.toggle('is-active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -5,28 +5,82 @@
|
|||||||
<h1 class="title">Abrechnung</h1>
|
<h1 class="title">Abrechnung</h1>
|
||||||
<h2 class="subtitle">Ausstehend</h2>
|
<h2 class="subtitle">Ausstehend</h2>
|
||||||
|
|
||||||
<table class="table">
|
<div class="notification is-info is-light is-hidden" id="noBalance">
|
||||||
|
Für diesen Benutzer stehen keine Transaktionen aus. <strong>Es gibt nichts zu bezahlen.</strong>
|
||||||
|
<br>
|
||||||
|
<button class="button is-info is-large" id="logout">Abmelden</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="balanceSheet">
|
||||||
|
<table class="table is-striped is-hoverable" id="payTable">
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><abbr title="Bezeichner">Bez.</abbr></th>
|
<th>Austellungsdatum</th>
|
||||||
<th>Preis</th>
|
<th>Preis</th>
|
||||||
<th></th>
|
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th id="table-sum"></th>
|
<th id="table-sum"></th>
|
||||||
<th></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
<tbody id="table-content">
|
<tbody id="table-content">
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<button class="button is-success is-large" id="paynow">Jetzt bezahlen <i class="bi bi-wallet2"></i></button>
|
||||||
|
</div>
|
||||||
|
<details>
|
||||||
|
<summary>Alle Transaktionen</summary>
|
||||||
|
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="transaction" data-pageSize="10" data-filters='{"user_id":-1}' data-loadmode="manual" id="alltransactions">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-dataCol = "id">Id</th>
|
||||||
|
<th data-dataCol = "total">Name</th>
|
||||||
|
<th data-dataCol = "paid" data-type="bool">Bezahlt</th>
|
||||||
|
<th data-dataCol = "createdAt" data-type="datetime">Ausgestellt am</th>
|
||||||
|
<th data-dataCol = "paidAt" data-type="datetime" data-order="DESC">Bezahlt am</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<nav class="pagination is-hidden" role="navigation" aria-label="pagination" data-targetTable="alltransactions">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Confirmation modal -->
|
||||||
|
<div class="modal" id="confirmModal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Bezahlung bestätigen</p>
|
||||||
|
<button class="delete" aria-label="close"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
Wurde der Betrag in die Kasse eingezahlt?
|
||||||
|
</p>
|
||||||
|
<h2 class="title is-2" id="ModalSum"></h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer class="modal-card-foot buttons">
|
||||||
|
<button class="button is-success" id="confirmCheckout">Bestätigen</button>
|
||||||
|
<button class="button" id="cancelCheckout">Abbrechen</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<%~ include("partials/footer.eta") %>
|
<%~ include("partials/footer.eta") %>
|
||||||
<script src="/static/pages/payup.js"></script>
|
<script src="/static/pages/payup.js"></script>
|
||||||
<%~ include("partials/base_foot.eta") %>
|
<%~ include("partials/base_foot.eta") %>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Empty sidebar on the right -->
|
<!-- Empty sidebar on the right -->
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
<h2 class="title is-4" >Ausgewählte Produkte</h2>
|
<h2 class="title is-4">Ausgewählte Produkte</h2>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -10,7 +10,12 @@
|
|||||||
<div id="date"></div>
|
<div id="date"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
const apiKey = "<%= it.apikey %>";
|
||||||
|
</script>
|
||||||
|
<script src="/static/apiWrapper.js"></script>
|
||||||
|
<script src="/static/pageDriver.js"></script>
|
||||||
<script src="/static/js/lockscreenBgHandler.js"></script>
|
<script src="/static/js/lockscreenBgHandler.js"></script>
|
||||||
|
<script src="/static/js/kiosk_mode.js"></script>
|
||||||
|
|
||||||
<%~ include("partials/base_foot.eta") %>
|
<%~ include("partials/base_foot.eta") %>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<%~ include("partials/base_head.eta", {"title": "Dashboard"}) %>
|
<%~ include("partials/base_head.eta", {"title": "Dashboard"}) %>
|
||||||
<%~ include("partials/nav.eta") %>
|
<%~ include("partials/nav.eta") %>
|
||||||
|
|
||||||
<section class="section buttons container" id="mainSelect">
|
<section class="section buttons container is-fluid is-centered" id="mainSelect">
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<hidden>
|
<hidden>
|
||||||
<!-- Base Button -->
|
<!-- Base Button -->
|
||||||
<button class="button is-link is-large m-2" id="baseStruct">Username</button>
|
<button class="button is-dark is-medium m-2" id="baseStruct">Username</button>
|
||||||
</hidden>
|
</hidden>
|
||||||
<div class="modal" id="pinPadModal">
|
<div class="modal" id="pinPadModal">
|
||||||
<div class="modal-background"></div>
|
<div class="modal-background"></div>
|
||||||
|
Reference in New Issue
Block a user