diff --git a/backend/db.js b/backend/db.js index efd3300..87c045a 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,59 +1,10 @@ -import { fileURLToPath } from "url"; -import { connection } from "./index.js"; -// Create the connection to database +import mysql from "mysql2/promise"; -// get data for users between 2 dates -async function getTopTen() { - let filteredResults; - let startDate = "2021-01-01"; - let endDate = "2029-01-01"; - if (req.query.to) startDate = req.query.to; - if (req.query.from) startDate = req.query.from; - try { - const [results, fields] = await connection.query( - "SELECT t.user,t.time,t.date,t.project \ - FROM Timelog t\ - WHERE t.date BETWEEN ? AND ? ;", - [startDate, endDate], - ); - filteredResults = results; - console.log(filteredResults); - } catch (err) { - console.log(err); - } +const connection = await mysql.createConnection({ + host: "localhost", + user: "monty", + password: "some_pass", + database: "timelog", +}); - if (req.query.filterBy) filterBy = req.query.filterBy; - // how much time each user has logged during that time period - if (filterBy === "user") { - let users = {}; - for (let i = 0; i < filteredResults.length; i++) { - if (filteredResults[i].user in users) { - users[filteredResults[i].user] += filteredResults[i].time; - } else { - users[filteredResults[i].user] = filteredResults[i].time; - } - } - // Create items array - let items = Object.keys(users).map(function (key) { - return [key, users[key]]; - }); - - // Sort the array based on the second element - items.sort(function (first, second) { - return second[1] - first[1]; - }); - - // Create a new array with only the first 10 items - return items.slice(0, 10); - } -} - -export { getTopTen }; - -// SELECT u.f_name,u.l_name,u.mail,p.name,t.time,t.date -// FROM Timelog t -// INNER JOIN Project p ON p.id=t.project -// INNER JOIN User u ON u.id=t.user -// WHERE User = "100"; -// ORDER BY time -// LIMIT 10 OFFSET 10 +export default connection; diff --git a/backend/index.js b/backend/index.js index 1b7f7c7..eff2223 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,179 +1,19 @@ import express from "express"; -import mysql from "mysql2/promise"; -import { getTopTen } from "./db.js"; -import { resourceUsage } from "process"; - -export const connection = await mysql.createConnection({ - host: "localhost", - user: "monty", - password: "some_pass", - database: "timelog", -}); -connection.config.namedPlaceholders = true; +import getAll from "./routers/getAll.js"; +import getUser from "./routers/getUser.js"; +import getTopTen from "./routers/getTopTen.js"; +import reset from "./routers/reset.js"; const router = express.Router(); -router.get("/getall", async (req, res) => { - let sql = - "SELECT u.f_name,u.l_name,u.mail,p.name,t.time,t.date,t.user \ - FROM Timelog t \ - INNER JOIN Project p ON p.id=t.project \ - INNER JOIN User u ON u.id=t.user "; - let where = "WHERE t.date BETWEEN ? AND ? "; - let order = "ORDER BY ?? "; - let offsetSql = "LIMIT 10 OFFSET ?;"; - let flag = 0; - if (req.query.from || req.query.to) { - sql = sql + where; - flag += 1; - } - if (req.query.sortby) { - sql = sql + order; - flag += 2; - } - sql = sql + offsetSql; - let results; - let fields; - if (flag == 0) { - [results, fields] = await connection.query(sql, [ - parseInt(req.query.offset), - ]); - } else if (flag === 1) { - [results, fields] = await connection.query(sql, [ - req.query.from, - req.query.to, - parseInt(req.query.offset), - ]); - } else if (flag === 2) { - [results, fields] = await connection.query(sql, [ - req.query.sortby, - parseInt(req.query.offset), - ]); - } else if (flag === 3) { - [results, fields] = await connection.query(sql, [ - req.query.from, - req.query.to, - req.query.sortby, - parseInt(req.query.offset), - ]); - } - res.json(results); -}); -router.get("/gettopten", async (req, res) => { - let filteredResults; - let results2; - let from = "2021-01-01"; - let to = "2029-01-01"; - let filterBy = "user"; - if (req.query.from) from = req.query.from; - if (req.query.to) to = req.query.to; - - try { - const [results, fields] = await connection.query( - "SELECT t.user,t.time,t.date,t.project \ - FROM Timelog t\ - WHERE t.date BETWEEN ? AND ? ;", - [from, to], - ); - filteredResults = results; - } catch (err) { - console.log(err); - } - - if (req.query.filterBy) filterBy = req.query.filterBy; - // how much time each user has logged during that time period - if (filterBy === "user") { - let users = {}; - for (let i = 0; i < filteredResults.length; i++) { - if (filteredResults[i].user in users) { - users[filteredResults[i].user] += filteredResults[i].time; - } else { - users[filteredResults[i].user] = filteredResults[i].time; - } - } - // Create items array - let items = Object.keys(users).map(function (key) { - return [key, users[key]]; - }); - - // Sort the array based on the second element - items.sort(function (first, second) { - return second[1] - first[1]; - }); - - // Create a new array with only the first 10 items - results2 = items.slice(0, 10); - } else if (filterBy === "project") { - let projects = {}; - for (let i = 0; i < filteredResults.length; i++) { - if (filteredResults[i].project in projects) { - projects[filteredResults[i].project] += filteredResults[i].time; - } else { - projects[filteredResults[i].project] = filteredResults[i].time; - } - } - // Create items array - let items = Object.keys(projects).map(function (key) { - return [key, projects[key]]; - }); - - // Sort the array based on the second element - items.sort(function (first, second) { - return second[1] - first[1]; - }); - - // Create a new array with only the first 10 items - results2 = items.slice(0, 10); - } - - res.json(results2); -}); -router.get("/reset", async (req, res) => { - try { - const [results, fields] = await connection.query("CALL InitDb;", []); - } catch (err) { - console.log(err); - } - res.status(200).json({ message: "Success" }); -}); -router.get("/getUser", async (req, res) => { - const userId = req.query.userid; - let results, fields; - console.log(userId); - try { - [results, fields] = await connection.query( - "SELECT p.name,t.time,t.project \ - FROM Timelog t \ - INNER JOIN Project p ON p.id=t.project \ - INNER JOIN User u ON u.id=t.user \ - WHERE USER = ? ;", - [userId], - ); - } catch (err) { - console.log(err); - } - let projects = {}; - let projectIdtoName = {}; - for (let i = 0; i < results.length; i++) { - if (results[i].project in projects) { - projects[results[i].project] += results[i].time; - } else { - projects[results[i].project] = results[i].time; - projectIdtoName[results[i].project] = results[i].name; - } - } - // map projectIds to project names before sending the data - let respData = {}; - for (let key in projects) { - if (projects.hasOwnProperty(key)) { - respData[projectIdtoName[key]] = projects[key]; - } - } - res.json(respData); -}); - const app = express(); app.use(express.json()); + +app.use("/api", getAll); +app.use("/api", getTopTen); +app.use("/api", getUser); +app.use("/api", reset); + app.use("/api", router); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server started on port ${PORT}`)); diff --git a/backend/temp.js b/backend/populateDB.js similarity index 100% rename from backend/temp.js rename to backend/populateDB.js diff --git a/backend/routers/getAll.js b/backend/routers/getAll.js new file mode 100644 index 0000000..561a22a --- /dev/null +++ b/backend/routers/getAll.js @@ -0,0 +1,60 @@ +import express from "express"; +import connection from "../db.js"; + +const router = express.Router(); + +router.get("/getall", async (req, res) => { + let sql = + "SELECT u.f_name,u.l_name,u.mail,p.name,t.time,t.date,t.user \ + FROM Timelog t \ + INNER JOIN Project p ON p.id=t.project \ + INNER JOIN User u ON u.id=t.user "; + const where = "WHERE t.date BETWEEN ? AND ? "; + const order = "ORDER BY ?? "; + const offsetSql = "LIMIT 10 OFFSET ?;"; + let flag = 0; + // construct the sql statement based on incoming request + if (req.query.from || req.query.to) { + sql = sql + where; + flag += 1; + } + if (req.query.sortby) { + sql = sql + order; + flag += 2; + } + sql = sql + offsetSql; + + let results; + let fields; + switch (flag) { + case 0: + [results, fields] = await connection.query(sql, [ + parseInt(req.query.offset), + ]); + break; + case 1: + [results, fields] = await connection.query(sql, [ + req.query.from, + req.query.to, + parseInt(req.query.offset), + ]); + break; + case 2: + [results, fields] = await connection.query(sql, [ + req.query.sortby, + parseInt(req.query.offset), + ]); + break; + case 3: + [results, fields] = await connection.query(sql, [ + req.query.from, + req.query.to, + req.query.sortby, + parseInt(req.query.offset), + ]); + break; + } + res.json(results); +}); + +export default router; diff --git a/backend/routers/getTopTen.js b/backend/routers/getTopTen.js new file mode 100644 index 0000000..d6115e6 --- /dev/null +++ b/backend/routers/getTopTen.js @@ -0,0 +1,69 @@ +import express from "express"; +import connection from "../db.js"; + +const router = express.Router(); + +router.get("/gettopten", async (req, res) => { + let from = "2020-01-01"; + let to = "2029-01-01"; + let filterBy = "user"; + if (req.query.from) from = req.query.from; + if (req.query.to) to = req.query.to; + if (req.query.filterBy) filterBy = req.query.filterBy; + let results, fields; + let filterBySql = filterBy === "user" ? "t.user" : "t.project"; + try { + [results, fields] = await connection.query( + "SELECT t.user,t.date,t.project,u.f_name,u.l_name,p.name,SUM(t.time) as total_time \ + FROM Timelog t \ + INNER JOIN Project p ON p.id=t.project \ + INNER JOIN User u ON u.id=t.user \ + WHERE t.date BETWEEN ? AND ? \ + GROUP BY ?? \ + ORDER BY total_time DESC\ + LIMIT 10;", + [from, to, filterBySql], + ); + } catch (err) { + console.log(err); + res.status(400).json({ message: "Error" }); + } + // how much time each user has logged during that time period + // let items = {}; + // let itemIdtoName = {}; + // for (let i = 0; i < results.length; i++) { + // if (results[i][filterBy] in items) { + // items[results[i][filterBy]] += results[i].time; + // } else { + // items[results[i][filterBy]] = results[i].time; + // if (filterBy === "user") + // itemIdtoName[results[i][filterBy]] = + // results[i].f_name + " " + results[i].l_name; + // else itemIdtoName[results[i][filterBy]] = results[i].name; + // } + // } + // let respData = {}; + // for (let key in items) { + // if (items.hasOwnProperty(key)) { + // respData[itemIdtoName[key]] = items[key]; + // } + // } + // // transform obj to array + // respData = Object.keys(items).map(function (key) { + // return [key, items[key]]; + // }); + // + // // sort the array based on the second element + // respData.sort(function (first, second) { + // return second[1] - first[1]; + // }); + // + // // console.log(itemIdtoName); + // // console.log(respData); + // // Create a new array with only the first 10 items + // respData = respData.slice(0, 10); + + res.json(results); +}); + +export default router; diff --git a/backend/routers/getUser.js b/backend/routers/getUser.js new file mode 100644 index 0000000..b52b3b3 --- /dev/null +++ b/backend/routers/getUser.js @@ -0,0 +1,42 @@ +import express from "express"; +import connection from "../db.js"; + +const router = express.Router(); + +router.get("/getUser", async (req, res) => { + const userId = req.query.userid; + let results, fields; + try { + [results, fields] = await connection.query( + "SELECT p.name,t.time,t.project \ + FROM Timelog t \ + INNER JOIN Project p ON p.id=t.project \ + INNER JOIN User u ON u.id=t.user \ + WHERE USER = ? ;", + [userId], + ); + } catch (err) { + console.log(err); + res.status(400).json("ERROR"); + } + let projects = {}; + let projectIdtoName = {}; + for (let i = 0; i < results.length; i++) { + if (results[i].project in projects) { + projects[results[i].project] += results[i].time; + } else { + projects[results[i].project] = results[i].time; + projectIdtoName[results[i].project] = results[i].name; + } + } + // map projectIds to project names before sending the data + let respData = {}; + for (let key in projects) { + if (projects.hasOwnProperty(key)) { + respData[projectIdtoName[key]] = projects[key]; + } + } + res.json(respData); +}); + +export default router; diff --git a/backend/routers/reset.js b/backend/routers/reset.js new file mode 100644 index 0000000..50400fe --- /dev/null +++ b/backend/routers/reset.js @@ -0,0 +1,16 @@ +import express from "express"; +import connection from "../db.js"; + +const router = express.Router(); + +router.get("/reset", async (req, res) => { + try { + await connection.query("CALL InitDb;", []); + } catch (err) { + console.log(err); + res.status(400).json({ message: "Error" }); + } + res.status(200).json({ message: "Success" }); +}); + +export default router; diff --git a/backend/sql/commands.sql b/backend/sql/commands.sql index 87db71c..e4dfe67 100644 --- a/backend/sql/commands.sql +++ b/backend/sql/commands.sql @@ -127,3 +127,11 @@ END$$ DELIMITER ; + SELECT t.user,t.date,t.project,u.f_name,u.l_name,p.name,SUM(t.time) as total_time + FROM Timelog t + INNER JOIN Project p ON p.id=t.project + INNER JOIN User u ON u.id=t.user + GROUP BY t.user + ORDER BY total_time DESC + LIMIT 10; + diff --git a/backend/sql/current.sql b/backend/sql/current.sql index 0078014..b0df285 100644 --- a/backend/sql/current.sql +++ b/backend/sql/current.sql @@ -84,14 +84,28 @@ BEGIN DECLARE hours FLOAT; DECLARE project INT; DECLARE curDate DATE DEFAULT "2024-11-18"; + DECLARE h2 INT; WHILE users <= 100 DO SET logs = FLOOR(1+(RAND()*20)); SET j=1; WHILE j <= logs DO - SET hours = (RAND() * (8 - 0.25)) + 0.25; - SET curDate = DATE_ADD(curDate, INTERVAL 1 DAY); SET project = FLOOR(1+(RAND()*3)); + SET curDate = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * DATEDIFF('2020-02-01', '2020-01-01')) DAY); + SET hours = (RAND() * (8 - 0.25)) + 0.25; + + SELECT SUM(time) INTO h2 + FROM Timelog + WHERE date = curdate && user = users ; + + WHILE (h2+hours) > 8 DO + SET curDate = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * DATEDIFF('2020-02-01', '2020-01-01')) DAY); + + SELECT SUM(time)INTO h2 + FROM Timelog + WHERE date = curdate && user = users ; + + END WHILE; INSERT INTO Timelog (user, project, date,time ) VALUES (users,project,curDate,hours); SET j=j+1; END WHILE; @@ -104,3 +118,28 @@ DELIMITER ; ## -- get data SELECT t.time,t.date,p.name,u.f_name,u.l_name,u.mail FROM Timelog t INNER JOIN Project p ON p.id=t.project INNER JOIN User u ON u.id=t.user; + +CREATE PROCEDURE fill_timelog () +BEGIN + DECLARE j INT DEFAULT 1; + DECLARE users INT DEFAULT 1; + DECLARE logs INT; + DECLARE hours FLOAT; + DECLARE project INT; + DECLARE curDate DATE DEFAULT "2024-11-18"; + +WHILE users <= 100 DO + SET logs = FLOOR(1+(RAND()*20)); + SET j=1; + WHILE j <= logs DO + SET hours = (RAND() * (8 - 0.25)) + 0.25; + SET project = FLOOR(1+(RAND()*3)); + SET curDate = DATE_ADD(curDate, INTERVAL 1 DAY); + INSERT INTO Timelog (user, project, date,time ) VALUES (users,project,curDate,hours); + SET j=j+1; + END WHILE; + SET users=users+1; +END WHILE; +END$$ + +DELIMITER ; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f9c5dc1..946873a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@emotion/styled": "^11.13.5", + "@mui/material": "^6.1.8", "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -45,7 +47,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", @@ -101,7 +102,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.26.2", @@ -135,7 +135,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", @@ -177,7 +176,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -187,7 +185,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -221,7 +218,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.26.0" @@ -265,11 +261,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.25.9", @@ -284,7 +291,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.25.9", @@ -303,7 +309,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -313,7 +318,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -323,6 +327,159 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.5.tgz", + "integrity": "sha512-Z3xbtJ+UcK76eWkagZ1onvn/wAVb1GOMuR15s30Fm2wrMgC7jzpnO2JZXr4eujTTqoQFUrZIw/rT0c6Zzjca1g==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.5.tgz", + "integrity": "sha512-6zeCUxUH+EPF1s+YF/2hPVODeV/7V07YU5x+2tfuRL8MdW6rv5vb2+CBEGTGwBdux0OIERcOS+RzxeK80k2DsQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.5.tgz", + "integrity": "sha512-gnOQ+nGLPvDXgIx119JqGalys64lhMdnNQA9TMxhDA4K0Hq5+++OE20Zs5GxiCV9r814xQ2K5WmtofSpHVW6BQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -921,7 +1078,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -936,7 +1092,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -946,7 +1101,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -956,20 +1110,222 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.8.tgz", + "integrity": "sha512-TGAvzwUg9hybDacwfIGFjI2bXYXrIqky+vMfaeay8rvT56/PNAlvIDUJ54kpT5KRc9AWAihOvtDI7/LJOThOmQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.8.tgz", + "integrity": "sha512-QZdQFnXct+7NXIzHgT3qt+sQiO7HYGZU2vymP9Xl9tUMXEOA/S1mZMMb7+WGZrk5TzNlU/kP/85K0da5V1jXoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.8", + "@mui/system": "^6.1.8", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.8", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.1.8", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.8.tgz", + "integrity": "sha512-TuKl7msynCNCVvhX3c0ef1sF0Qb3VHcPs8XOGB/8bdOGBr/ynmIG1yTMjZeiFQXk8yN9fzK/FDEKMFxILNn3wg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.8", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.8.tgz", + "integrity": "sha512-ZvEoT0U2nPLSLI+B4by4cVjaZnPT2f20f4JUPkyHdwLv65ZzuoHiTlwyhqX1Ch63p8bcJzKTHQVGisEoMK6PGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.8.tgz", + "integrity": "sha512-i1kLfQoWxzFpXTBQIuPoA3xKnAnP3en4I2T8xIolovSolGQX5k8vGjw1JaydQS40td++cFsgCdEU458HDNTGUA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.8", + "@mui/styled-engine": "^6.1.8", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.8", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.8.tgz", + "integrity": "sha512-O2DWb1kz8hiANVcR7Z4gOB3SvPPsSQGUmStpyBDzde6dJIfBzgV9PbEQOBZd3EBsd1pB+Uv1z5LAJAbymmawrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1008,6 +1364,16 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.27.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.2.tgz", @@ -1319,18 +1685,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1347,6 +1717,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", @@ -1689,6 +2068,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1757,7 +2151,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1801,6 +2194,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1847,6 +2249,22 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -1866,14 +2284,12 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1903,6 +2319,16 @@ "node": ">=0.4.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.62", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.62.tgz", @@ -1910,6 +2336,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1963,7 +2398,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2236,6 +2670,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2323,6 +2763,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2376,6 +2825,35 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2390,7 +2868,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -2413,6 +2890,27 @@ "node": ">=0.8.19" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2476,7 +2974,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2492,6 +2989,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2543,6 +3046,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2650,7 +3159,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -2686,6 +3194,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2740,7 +3257,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -2749,6 +3265,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2769,11 +3303,25 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2828,6 +3376,23 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -2900,6 +3465,12 @@ "react-dom": ">=16.3.0" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -2910,11 +3481,49 @@ "node": ">=0.10.0" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -3035,6 +3644,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3058,6 +3676,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3071,6 +3695,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3282,6 +3918,15 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index becd3f8..0eba5ce 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/styled": "^11.13.5", + "@mui/material": "^6.1.8", "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/src/App.css b/frontend/src/App.css index b9d355d..8b13789 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,42 +1 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e63b4b6..860d878 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,18 @@ import "./App.css"; import LeftSide from "./components/LeftSide"; import RightSide from "./components/RightSide"; +import Grid from "@mui/material/Grid2"; function App() { return ( -
- - -
+ + + + + + + + ); } diff --git a/frontend/src/components/LeftSide.tsx b/frontend/src/components/LeftSide.tsx index 47a1a05..aecab08 100644 --- a/frontend/src/components/LeftSide.tsx +++ b/frontend/src/components/LeftSide.tsx @@ -1,15 +1,27 @@ import { useEffect, useState } from "react"; import api from "../utils/api"; +import Grid from "@mui/material/Grid2"; +import { + TableRow, + TableBody, + TableCell, + TableHead, + Table, +} from "@mui/material"; const LeftSide = () => { + // next prev and sort const [users, setUsers] = useState(); const [params, setParams] = useState({ offset: 0, sortby: "f_name" }); - + // reset button const [reset, setReset] = useState(true); // date const [date, setDate] = useState({ from: "2021-01-01", to: "2028-01-01" }); - const [dateToSubmit, setDateToSubmit] = useState({}); + const [dateToSubmit, setDateToSubmit] = useState({ + from: "2021-01-01", + to: "2028-01-01", + }); useEffect(() => { async function fetchData() { @@ -28,13 +40,7 @@ const LeftSide = () => { fetchData(); }, [reset, params, dateToSubmit]); - const handleButtonClick = () => { - // When the button is clicked, update the dateToShow state and trigger a re-render - setDateToSubmit(date); - }; - const viewProjectHours = (user) => { - console.log(user); async function fetchHours() { const resp = await api.get("/getUser", { params: { userid: user }, @@ -47,8 +53,8 @@ const LeftSide = () => { if (!users) return <>; return ( -
-
+ + { value={date.to} onChange={(event) => setDate({ ...date, to: event.target.value })} /> - -
-
-

Select a sort:

+ + + + Select a sort: -
- - - - - - - - - - - - - - + + + + + + + +
First NameLast NameEmailProject nameDateHours
+ + + First Name + Last Name + Email + Project name + Date + Hours + + + {users.length > 0 && Array.isArray(users) ? ( users.map((post, idx) => ( - - - - - - - - - + + )) ) : (
No posts found...
)} - -
{post.f_name} {post.l_name} {post.mail} {post.name} {post.date} {post.time} + + {post.f_name} + {post.l_name} + {post.mail} + {post.name} + {post.date.slice(0, 10)} + {post.time} + -
- -
+ + + + + + + ); }; diff --git a/frontend/src/components/RightSide.tsx b/frontend/src/components/RightSide.tsx index 285feaf..f881425 100644 --- a/frontend/src/components/RightSide.tsx +++ b/frontend/src/components/RightSide.tsx @@ -3,22 +3,39 @@ import api from "../utils/api"; import { Chart } from "react-google-charts"; const RightSide = () => { - const [topten, setTopten] = useState(); - // input field - const [date, setDate] = useState({ from: "2021-01-01", to: "2028-01-01" }); + // graph date + const [topten, setTopten] = useState({ + from: "2000-01-01", + to: "2100-01-01", + }); + // date input field + const [date, setDate] = useState({ from: "2000-01-01", to: "2100-01-01" }); const [dateToSubmit, setDateToSubmit] = useState({}); - // radio + // radio button const [selectedOption, setSelectedOption] = useState("project"); + useEffect(() => { async function fetchData() { const resp = await api.get("/gettopten", { params: { - to: dateToSubmit.to, from: dateToSubmit.from, + to: dateToSubmit.to, filterBy: selectedOption, }, }); resp.data.unshift(["User", "Hours"]); + // turn the data into form suitable to charts + if (selectedOption === "project") + for (let idx = 1; idx < resp.data.length; idx++) { + resp.data[idx] = [resp.data[idx].name, resp.data[idx].total_time]; + } + else if (selectedOption === "user") + for (let idx = 1; idx < resp.data.length; idx++) { + resp.data[idx] = [ + resp.data[idx].f_name + " " + resp.data[idx].l_name, + resp.data[idx].total_time, + ]; + } setTopten(resp.data); } fetchData(); @@ -27,29 +44,21 @@ const RightSide = () => { // Chart options const options = { title: "Most comitted hours per user", - chartArea: { width: "50%" }, + chartArea: { width: "70%" }, hAxis: { title: "Hours", minValue: 0, }, vAxis: { - title: "User", + title: selectedOption, }, }; - const handleButtonClick = () => { - // When the button is clicked, update the dateToShow state and trigger a re-render - setDateToSubmit(date); - }; - - // Handle the change when a radio button is selected - const handleChange = (event) => { - setSelectedOption(event.target.value); - }; if (!topten) <>; + return ( -
-
+
+ <> { value={date.to} onChange={(event) => setDate({ ...date, to: event.target.value })} /> - -
-
+ + + <> -
- -
+