mirror of
https://github.com/DerTyp7/time-tracking.git
synced 2025-10-29 12:32:11 +01:00
table and entries
This commit is contained in:
@@ -1 +1,25 @@
|
||||
// ExpressJS server
|
||||
// Dependencies
|
||||
const express = require("express");
|
||||
const configFile = require("./config");
|
||||
const logger = require("./logger");
|
||||
const cors = require("cors");
|
||||
|
||||
// Global variables
|
||||
const app = express();
|
||||
const config = configFile.config;
|
||||
const port = config.port;
|
||||
|
||||
// Routes
|
||||
const apiEntry = require("./routes/api/entry");
|
||||
|
||||
// Set up the express server
|
||||
app.use(cors());
|
||||
|
||||
app.use(express.json()); // Parse JSON data from requests
|
||||
app.use(express.urlencoded({ extended: true })); // Parse URL encoded data from requests
|
||||
|
||||
// routes
|
||||
app.use("/api/entry", apiEntry);
|
||||
|
||||
// Start the express server
|
||||
app.listen(port, () => logger.info("Server started", `Port: ${port}`));
|
||||
|
||||
11
backend/config.js
Normal file
11
backend/config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const config = {
|
||||
db: {
|
||||
host: "127.0.0.1",
|
||||
user: "root",
|
||||
password: "", // In production: "InfraTag!" SORRY FOR CLEAR TEXT PASSWORD
|
||||
database: "rme-time-tracking",
|
||||
},
|
||||
port: 8080, // Port for the server
|
||||
};
|
||||
|
||||
module.exports = { config };
|
||||
34
backend/handlers/mysql_handler.js
Normal file
34
backend/handlers/mysql_handler.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Handles the connection to the MySQL database
|
||||
* - Connects to the database
|
||||
*/
|
||||
|
||||
// Dependencies
|
||||
const mysql = require("mysql");
|
||||
const configFile = require("../config");
|
||||
const logger = require("../logger");
|
||||
|
||||
// Global variables
|
||||
const config = configFile.config;
|
||||
|
||||
// Connection to the database
|
||||
const con = mysql.createConnection({
|
||||
host: config.db.host,
|
||||
user: config.db.user,
|
||||
password: config.db.password,
|
||||
database: config.db.database,
|
||||
});
|
||||
|
||||
con.connect(function (err) {
|
||||
if (err) throw err;
|
||||
logger.info(
|
||||
`Connected to MySQL`,
|
||||
`${config.db.user}@${config.db.host}/${config.db.database}`
|
||||
);
|
||||
});
|
||||
|
||||
con.on("error", () => {
|
||||
logger.error("MySQL Error");
|
||||
});
|
||||
|
||||
module.exports = { con };
|
||||
25
backend/handlers/request_handler.js
Normal file
25
backend/handlers/request_handler.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// Handlers which can be included into the request handler chain.
|
||||
|
||||
// Dependencies
|
||||
const logger = require("../logger");
|
||||
|
||||
// better logging of requests
|
||||
function LoggerHandler(req, res, next) {
|
||||
const ip = (req.headers["x-forwarded-for"] || req.socket.remoteAddress || "")
|
||||
.split(",")[0]
|
||||
.trim()
|
||||
.split(":")[3]; // get the ip of the client
|
||||
|
||||
if (req.method == "GET") {
|
||||
// If the request is a GET request
|
||||
logger.get(req.originalUrl, ip); // Log the request
|
||||
} else if (req.method == "POST") {
|
||||
// If the request is a POST request
|
||||
logger.post(req.originalUrl, ip); // Log the request
|
||||
}
|
||||
next(); // Continue to the next handler
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
LoggerHandler,
|
||||
};
|
||||
103
backend/logger.js
Normal file
103
backend/logger.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const SHOW_TIME = true;
|
||||
|
||||
function getTimestamp() {
|
||||
let date_ob = new Date();
|
||||
|
||||
// current date
|
||||
// adjust 0 before single digit date
|
||||
let date = ("0" + date_ob.getDate()).slice(-2);
|
||||
|
||||
// current month
|
||||
let month = ("0" + (date_ob.getMonth() + 1)).slice(-2);
|
||||
|
||||
// current year
|
||||
let year = date_ob.getFullYear();
|
||||
|
||||
// current hours
|
||||
let hours = ("0" + date_ob.getHours()).slice(-2);
|
||||
|
||||
// current minutes
|
||||
let minutes = ("0" + date_ob.getMinutes()).slice(-2);
|
||||
|
||||
// current seconds
|
||||
let seconds = ("0" + date_ob.getSeconds()).slice(-2);
|
||||
|
||||
if (SHOW_TIME) {
|
||||
return hours + ":" + minutes + ":" + seconds;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function cmd(msg, info = null) {
|
||||
console.log(
|
||||
"\x1b[36m%s\x1b[0m",
|
||||
`${
|
||||
getTimestamp() != null ? "[" + getTimestamp() + "]" : ""
|
||||
}[COMMAND] ${msg} ${info != null ? "- " + info : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
function info(msg, info = null) {
|
||||
console.log(
|
||||
"\x1b[37m%s\x1b[0m",
|
||||
`${getTimestamp() != null ? "[" + getTimestamp() + "]" : ""}[INFO] ${msg} ${
|
||||
info != null ? "- " + info : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
function error(msg, info = null) {
|
||||
console.log(
|
||||
"\x1b[31m%s\x1b[0m",
|
||||
`${
|
||||
getTimestamp() != null ? "[" + getTimestamp() + "]" : ""
|
||||
}[ERROR] ${msg} ${info != null ? "- " + info : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
function get(msg, info = null) {
|
||||
console.log(
|
||||
"\x1b[37m\x1b[2m%s\x1b[0m",
|
||||
`${getTimestamp() != null ? "[" + getTimestamp() + "]" : ""}[GET] ${msg} ${
|
||||
info != null ? "- " + info : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
function post(msg, info = null) {
|
||||
console.log(
|
||||
"\x1b[37m\x1b[2m%s\x1b[0m",
|
||||
`${getTimestamp() != null ? "[" + getTimestamp() + "]" : ""}[POST] ${msg} ${
|
||||
info != null ? "- " + info : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
function socket(msg, type) {
|
||||
console.log(
|
||||
"\x1b[35m\x1b[1m%s\x1b[0m",
|
||||
`${
|
||||
getTimestamp() != null ? "[" + getTimestamp() + "]" : ""
|
||||
}[SOCKET.IO][${type}] ${msg}`
|
||||
);
|
||||
}
|
||||
|
||||
function event(msg, type) {
|
||||
console.log(
|
||||
"\x1b[35m\x1b[1m%s\x1b[0m",
|
||||
`${
|
||||
getTimestamp() != null ? "[" + getTimestamp() + "]" : ""
|
||||
}[EVENT][${type}] ${msg}`
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cmd,
|
||||
info,
|
||||
error,
|
||||
get,
|
||||
post,
|
||||
socket,
|
||||
event,
|
||||
};
|
||||
209
backend/package-lock.json
generated
209
backend/package-lock.json
generated
@@ -9,7 +9,9 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.1"
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.1",
|
||||
"mysql": "^2.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
@@ -29,6 +31,14 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
|
||||
@@ -104,6 +114,23 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -308,6 +335,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -364,6 +396,25 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node_modules/mysql": {
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
|
||||
"dependencies": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"readable-stream": "2.3.7",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sqlstring": "2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@@ -372,6 +423,14 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
|
||||
@@ -404,6 +463,11 @@
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@@ -452,6 +516,25 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -536,6 +619,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sqlstring": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
||||
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
@@ -544,6 +635,19 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@@ -572,6 +676,11 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
@@ -604,6 +713,11 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
|
||||
@@ -660,6 +774,20 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -818,6 +946,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -856,11 +989,34 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"mysql": {
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
|
||||
"requires": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"readable-stream": "2.3.7",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sqlstring": "2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
|
||||
@@ -884,6 +1040,11 @@
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@@ -917,6 +1078,27 @@
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -980,11 +1162,31 @@
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"sqlstring": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
||||
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@@ -1004,6 +1206,11 @@
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.1"
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.1",
|
||||
"mysql": "^2.18.1"
|
||||
}
|
||||
}
|
||||
|
||||
118
backend/routes/api/entry.js
Normal file
118
backend/routes/api/entry.js
Normal file
@@ -0,0 +1,118 @@
|
||||
// Dependencies
|
||||
const express = require("express");
|
||||
const mysql = require("../../handlers/mysql_handler");
|
||||
const logger = require("../../logger");
|
||||
const request_handler = require("../../handlers/request_handler");
|
||||
|
||||
// Global variables
|
||||
const con = mysql.con;
|
||||
const router = express.Router();
|
||||
|
||||
function formatDate(date) {
|
||||
let result = date.split("T")[0].split("-");
|
||||
result = result[2] + "." + result[1] + "." + result[0];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Calculate time difference
|
||||
function calcInd(date, checkedIn, checkedOut) {
|
||||
date = date.split("T")[0];
|
||||
let ind =
|
||||
(new Date(date + "T" + checkedOut) - new Date(date + "T" + checkedIn)) /
|
||||
1000 /
|
||||
60 /
|
||||
60;
|
||||
|
||||
// round ind to 2 decimal places
|
||||
ind = Math.round(ind * 100) / 100;
|
||||
|
||||
if (ind > 6) {
|
||||
ind -= 0.5;
|
||||
}
|
||||
|
||||
return ind;
|
||||
}
|
||||
|
||||
//! Bei dieser Funktion habe 112312 Gehirnzellen verloren. Bitte nicht drauf ansprechen.
|
||||
function calcNorm(date, checkedIn, checkedOut) {
|
||||
let ind = calcInd(date, checkedIn, checkedOut).toString().split(".");
|
||||
|
||||
let hours = parseInt(ind[0]);
|
||||
let minutes = 0;
|
||||
|
||||
if (ind.length > 1) {
|
||||
minutes = parseInt(ind[1] * 6) / 10;
|
||||
minutes = Math.round(minutes);
|
||||
}
|
||||
|
||||
if (hours < 10) {
|
||||
hours = "0" + hours;
|
||||
}
|
||||
if (minutes < 10) {
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
|
||||
return hours + ":" + minutes;
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
let result = time.split(":");
|
||||
result = result[0] + ":" + result[1];
|
||||
return result;
|
||||
}
|
||||
|
||||
function isInMonth(date, monthYear) {
|
||||
if (monthYear == undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
date = date.split("-");
|
||||
let month = parseInt(date[1]);
|
||||
let year = parseInt(date[0]);
|
||||
monthYear = monthYear.split("-");
|
||||
if (month == monthYear[0] && year == monthYear[1]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// monthYear = month number-year number (04-2019)
|
||||
router.get("/all/:monthYear?", request_handler.LoggerHandler, (req, res) => {
|
||||
const monthYear = req.params.monthYear;
|
||||
|
||||
con.query(`SELECT * FROM entries ORDER BY date ASC`, (err, result) => {
|
||||
if (err) {
|
||||
logger.error(err, "routes/api/entries.js");
|
||||
res.send(JSON.parse(JSON.stringify({ error: err }))); // Send error message
|
||||
return;
|
||||
} else {
|
||||
let entries = [];
|
||||
result = JSON.parse(JSON.stringify(result));
|
||||
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
if (isInMonth(result[i].date, monthYear)) {
|
||||
entries.push({
|
||||
date: formatDate(result[i].date),
|
||||
checkedIn: formatTime(result[i].checked_in),
|
||||
checkedOut: formatTime(result[i].checked_out),
|
||||
ind: calcInd(
|
||||
result[i].date,
|
||||
result[i].checked_in,
|
||||
result[i].checked_out
|
||||
),
|
||||
norm: calcNorm(
|
||||
result[i].date,
|
||||
result[i].checked_in,
|
||||
result[i].checked_out
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.send(entries); // Send result
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
58
frontend/package-lock.json
generated
58
frontend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "^1.51.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
@@ -7991,6 +7992,14 @@
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/history": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
||||
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.6"
|
||||
}
|
||||
},
|
||||
"node_modules/hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
@@ -13425,6 +13434,30 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||
"dependencies": {
|
||||
"history": "^5.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||
"dependencies": {
|
||||
"history": "^5.2.0",
|
||||
"react-router": "6.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@@ -21933,6 +21966,14 @@
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
},
|
||||
"history": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
||||
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.6"
|
||||
}
|
||||
},
|
||||
"hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
@@ -25729,6 +25770,23 @@
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||
"requires": {
|
||||
"history": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||
"requires": {
|
||||
"history": "^5.2.0",
|
||||
"react-router": "6.3.0"
|
||||
}
|
||||
},
|
||||
"react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "^1.51.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import "./css/App.scss";
|
||||
import "./css/app.scss";
|
||||
import ServerProvider from "./contexts/ServerContext";
|
||||
import Table from "./components/table/Table";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div id="app">
|
||||
<div id="content" className="app-container">
|
||||
<h1>Hello World</h1>
|
||||
<ServerProvider>
|
||||
<div id="app">
|
||||
<div id="content" className="app-container">
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route exact path="/table/:monthYear" element={<Table />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
<div id="sidebar" className="app-container">
|
||||
<h1>Sidebar</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sidebar" className="app-container">
|
||||
<h1>Sidebar</h1>
|
||||
</div>
|
||||
</div>
|
||||
</ServerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
25
frontend/src/components/table/Header.jsx
Normal file
25
frontend/src/components/table/Header.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import "../../css/components/table.scss";
|
||||
|
||||
function Header({ date, checkedIn, checkedOut, ind, norm }) {
|
||||
return (
|
||||
<div className="table-header">
|
||||
<div className="table-cell">
|
||||
<span>{date}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{checkedIn}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{checkedOut}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{ind}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{norm}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
25
frontend/src/components/table/Row.jsx
Normal file
25
frontend/src/components/table/Row.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import "../../css/components/table.scss";
|
||||
|
||||
function Row({ date, checkedIn, checkedOut, ind, norm }) {
|
||||
return (
|
||||
<div className="table-row">
|
||||
<div className="table-cell">
|
||||
<span>{date ? date : "-"}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{checkedIn ? checkedIn : "-"}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{checkedOut ? checkedOut : "-"}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{ind ? ind : "-"}</span>
|
||||
</div>
|
||||
<div className="table-cell">
|
||||
<span>{norm ? norm : "-"}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Row;
|
||||
81
frontend/src/components/table/Table.jsx
Normal file
81
frontend/src/components/table/Table.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import { ServerContext } from "../../contexts/ServerContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import "../../css/components/table.scss";
|
||||
|
||||
import Row from "./Row";
|
||||
import Header from "./Header";
|
||||
|
||||
function Table() {
|
||||
const params = useParams();
|
||||
const { URL } = useContext(ServerContext);
|
||||
const [entries, setEntries] = useState([]);
|
||||
const [monthYear, setMonthYear] = useState(params.monthYear);
|
||||
|
||||
async function fetchEntries() {
|
||||
let response = await fetch(URL + `/api/entry/all/` + monthYear);
|
||||
let data = await response.json();
|
||||
setEntries(data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntries();
|
||||
}, []);
|
||||
|
||||
function nextMonth() {
|
||||
let month = parseInt(monthYear.split("-")[0]);
|
||||
|
||||
if (month === 12) {
|
||||
window.location.href = `/table/${1}-${
|
||||
parseInt(monthYear.split("-")[1]) + 1
|
||||
}`;
|
||||
} else {
|
||||
window.location.href = `/table/${month + 1}-${monthYear.split("-")[1]}`;
|
||||
}
|
||||
}
|
||||
|
||||
function previousMonth() {
|
||||
let month = parseInt(monthYear.split("-")[0]);
|
||||
|
||||
if (month === 1) {
|
||||
window.location.href = `/table/${12}-${
|
||||
parseInt(monthYear.split("-")[1]) - 1
|
||||
}`;
|
||||
} else {
|
||||
window.location.href = `/table/${month - 1}-${monthYear.split("-")[1]}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="table">
|
||||
<div className="table-nav">
|
||||
<div onClick={previousMonth}> </div>
|
||||
<h2>{monthYear}</h2>
|
||||
<div onClick={nextMonth}> </div>
|
||||
</div>
|
||||
<div className="table-container">
|
||||
<Header
|
||||
date="Date"
|
||||
checkedIn="Checked In"
|
||||
checkedOut="Checked Out"
|
||||
ind="Ind."
|
||||
norm="Norm."
|
||||
/>
|
||||
|
||||
{/* ROWS */}
|
||||
{entries.map((entry, index) => (
|
||||
<Row
|
||||
date={entry.date}
|
||||
checkedIn={entry.checkedIn}
|
||||
checkedOut={entry.checkedOut}
|
||||
ind={entry.ind}
|
||||
norm={entry.norm}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Table;
|
||||
14
frontend/src/contexts/ServerContext.jsx
Normal file
14
frontend/src/contexts/ServerContext.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React, { createContext } from "react";
|
||||
|
||||
export const ServerContext = createContext();
|
||||
|
||||
const ServerProvider = (props) => {
|
||||
const URL = "http://127.0.0.1:8080";
|
||||
return (
|
||||
<ServerContext.Provider value={{ URL }}>
|
||||
{props.children}
|
||||
</ServerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerProvider;
|
||||
59
frontend/src/css/components/table.scss
Normal file
59
frontend/src/css/components/table.scss
Normal file
@@ -0,0 +1,59 @@
|
||||
#table {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.table-nav {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
div {
|
||||
background-color: red;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
|
||||
.table-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
width: 100%;
|
||||
background-color: rgb(28, 28, 28);
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid rgb(157, 157, 157);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
width: 100%;
|
||||
background-color: rgb(61, 61, 61);
|
||||
border-top: 1px solid rgb(85, 85, 85);
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
grid-area: auto;
|
||||
padding: 10px;
|
||||
border-right: 1px solid rgb(85, 85, 85);
|
||||
border-left: 1px solid rgb(85, 85, 85);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user