From 64ea467e3c62e0acd44a092e8bdf64984fe751b1 Mon Sep 17 00:00:00 2001 From: Ipmake Date: Mon, 11 Mar 2024 23:52:37 +0100 Subject: [PATCH] Gay ass --- backend/prisma/schema.prisma | 6 +- backend/src/index.ts | 261 +++++-- backend/src/types.d.ts | 6 +- frontend/package-lock.json | 179 ++++- frontend/package.json | 5 + frontend/src/components/Sidebar.tsx | 4 +- frontend/src/components/UserModal.tsx | 160 +---- frontend/src/functions.ts | 2 +- frontend/src/index.tsx | 8 +- frontend/src/pages/AdminFrame.tsx | 4 +- frontend/src/pages/LandingPage.tsx | 111 ++- frontend/src/pages/admin/Benutzer.tsx | 13 - frontend/src/pages/admin/Dashboard.tsx | 121 +--- frontend/src/pages/admin/ManageData.tsx | 899 ++++++++++++++++++++++++ frontend/src/types.d.ts | 15 +- 15 files changed, 1378 insertions(+), 416 deletions(-) create mode 100644 frontend/src/pages/admin/ManageData.tsx diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 479962b..b61a2c5 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -9,14 +9,14 @@ datasource db { extensions = [uuid_ossp(map: "uuid-ossp")] } -model users { - ID String @id @default(dbgenerated("uuid_generate_v1()")) @db.Uuid +model Users { + ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid username String password String @db.VarChar(128) token String @db.VarChar(128) } -model Mitarbeiter { +model Employees { ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid Vorname String Nachname String diff --git a/backend/src/index.ts b/backend/src/index.ts index 6179324..224d421 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -62,7 +62,7 @@ app.get('/api/auth/verify', async (req, res) => { res.send('OK') }) -app.post("/api/worker/create", async (req, res) => { +app.put("/api/employees", async (req, res) => { const token = req.headers.authorization?.split(' ')[1] if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) @@ -70,13 +70,43 @@ app.post("/api/worker/create", async (req, res) => { const user = await authorize(token) if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) - const { title, content, image, sponsors } = req.body - if (!title || !content || !image) return res.status(400).send(Errors.MISSING_ITEMS) + if (!Array.isArray(req.body)) return res.status(400).send(Errors.INVALID_ITEMS) - // sponsors is an array of sponsor IDs + const employees: { + ID: string; + Vorname: string; + Nachname: string; + Geburtstag?: number | Date; + Anstelldatum?: number | Date; + }[] = req.body; + + for (let i in employees) { + if ( + !employees[i].Vorname + || !employees[i].Nachname + || employees[i].ID + ) return res.status(400).send(Errors.MISSING_ITEMS) + + if (employees[i].Geburtstag && typeof employees[i].Geburtstag !== 'number') return res.status(400).send(Errors.INVALID_ITEMS) + if (employees[i].Anstelldatum && typeof employees[i].Anstelldatum !== 'number') return res.status(400).send(Errors.INVALID_ITEMS) + + if (employees[i].Geburtstag && typeof employees[i].Geburtstag === 'number') employees[i].Geburtstag = new Date(employees[i].Geburtstag as number) + if (employees[i].Anstelldatum && typeof employees[i].Anstelldatum === 'number') employees[i].Anstelldatum = new Date(employees[i].Anstelldatum as number) + } + + const newEmployees = await prisma.employees.createMany({ + data: employees as { + Vorname: string; + Nachname: string; + Geburtstag?: Date; + Anstelldatum?: Date; + }[] + }) + + res.send(newEmployees) }) -app.get('/api/workers', async (req, res) => { +app.patch("/api/employees/:id", async (req, res) => { const token = req.headers.authorization?.split(' ')[1] if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) @@ -84,18 +114,128 @@ app.get('/api/workers', async (req, res) => { const user = await authorize(token) if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) - const workers = await prisma.mitarbeiter.findMany({ + let { Vorname, Nachname, Geburtstag, Anstelldatum } = req.body as { + Vorname: string; + Nachname: string; + Geburtstag?: number | Date; + Anstelldatum?: number | Date; + } + if (!Vorname || !Nachname) return res.status(400).send(Errors.MISSING_ITEMS) + + + if (Geburtstag && typeof Geburtstag !== 'number') return res.status(400).send(Errors.INVALID_ITEMS) + if (Anstelldatum && typeof Anstelldatum !== 'number') return res.status(400).send(Errors.INVALID_ITEMS) + + if (Geburtstag && typeof Geburtstag === 'number') Geburtstag = new Date(Geburtstag) + if (Anstelldatum && typeof Anstelldatum === 'number') Anstelldatum = new Date(Anstelldatum) + + const updatedEmployee = await prisma.employees.update({ + where: { + ID: req.params.id + }, + data: { + Vorname, + Nachname, + Geburtstag: (Geburtstag as Date | undefined), + Anstelldatum: (Anstelldatum as Date | undefined) + } + }) + + res.send(updatedEmployee) +}) + +app.patch("/api/employees/", async (req, res) => { + const token = req.headers.authorization?.split(' ')[1] + + if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) + + const user = await authorize(token) + if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) + + if (!Array.isArray(req.body)) return res.status(400).send(Errors.INVALID_ITEMS) + + const employees: { + ID: string; + Vorname: string; + Nachname: string; + Geburtstag?: number | Date; + Anstelldatum?: number | Date; + }[] = req.body; + + for (let i in employees) { + if ( + !employees[i].Vorname + || !employees[i].Nachname + || !employees[i].ID + ) return res.status(400).send(Errors.MISSING_ITEMS) + + if (employees[i].Geburtstag && typeof employees[i].Geburtstag !== 'number') return res.status(400).send(Errors.INVALID_ITEMS) + if (employees[i].Anstelldatum && typeof employees[i].Anstelldatum !== 'number') return res.status(400).send(Errors.INVALID_ITEMS) + + if (employees[i].Geburtstag && typeof employees[i].Geburtstag === 'number') employees[i].Geburtstag = new Date(employees[i].Geburtstag as number) + if (employees[i].Anstelldatum && typeof employees[i].Anstelldatum === 'number') employees[i].Anstelldatum = new Date(employees[i].Anstelldatum as number) + } + + const updatedEmployees = await Promise.all(employees.map(employee => + prisma.employees.update({ + where: { ID: employee.ID }, + data: { + Vorname: employee.Vorname, + Nachname: employee.Nachname, + Geburtstag: employee.Geburtstag as Date | undefined, + Anstelldatum: employee.Anstelldatum as Date | undefined + } + }) + )) + + res.send(updatedEmployees) +}) + +app.get('/api/employees', async (req, res) => { + const token = req.headers.authorization?.split(' ')[1] + + if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) + + const user = await authorize(token) + if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) + + const workers = await prisma.employees.findMany({ select: { + ID: true, Vorname: true, Nachname: true, Geburtstag: true, Anstelldatum: true, + }, + orderBy: { + Nachname: 'asc' } }) res.send(workers) }) +app.delete('/api/employees/:employees', async (req, res) => { + const token = req.headers.authorization?.split(' ')[1] + + if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) + + const user = await authorize(token) + if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) + + const employees: string[] = req.params.employees.split(',') + + await prisma.employees.deleteMany({ + where: { + ID: { + in: employees + } + } + }) + + res.send('OK') +}) + app.get('/api/users', async (req, res) => { const token = req.headers.authorization?.split(' ')[1] @@ -172,8 +312,8 @@ app.patch('/api/users/edit/:id', async (req, res) => { if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) - const { username, password, admin, article_create, article_manage, sponsor_manage, user_manage } = req.body - if (!username || admin === undefined || article_create === undefined || article_manage === undefined || sponsor_manage === undefined || user_manage === undefined) return res.status(400).send(Errors.MISSING_ITEMS) + const { username, password } = req.body + if (!username) return res.status(400).send(Errors.MISSING_ITEMS) const newUser = await prisma.users.update({ where: { @@ -181,11 +321,6 @@ app.patch('/api/users/edit/:id', async (req, res) => { }, data: { username, - admin, - article_create, - article_manage, - sponsor_manage, - user_manage, ...(password && { password }) } }) @@ -211,65 +346,56 @@ app.delete('/api/users/delete/:id', async (req, res) => { - app.get('/api/toshow/:targetDate?', async (req, res) => { const { targetDate } = req.params; - if(targetDate && targetDate.length !== 10) return res.status(400).send(Errors.INVALID_ITEMS) - const token = req.headers.authorization?.split(' ')[1] + if (targetDate && targetDate.length !== 10) return res.status(400).send(Errors.INVALID_ITEMS) - if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) + let dates: Types.EmployeeEvent[] = await prisma.employees.findMany({}) - const user = await authorize(token) - if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) - - let dates: Types.Mitarbeiter[] = await prisma.mitarbeiter.findMany({}) - - let currDate; - if(targetDate) currDate = moment(targetDate, "YYYY-MM-DD"); + let currDate; + if (targetDate) currDate = moment(targetDate, "YYYY-MM-DD"); else currDate = moment(); for (let i in dates) { let validity = false; - - // `${currDate.getFullYear()}-${getFormatNumber(date.getMonth(), 2)}-${getFormatNumber(date.getDay(), 2)}` - + if (dates[i].Anstelldatum) { const date = dates[i].Anstelldatum; const dateWithoutYear = moment(date).format("MM-DD"); if ( - date && + date && ( ( - moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD") <= currDate - ) + moment(dateWithoutYear, "MM-DD") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD") <= currDate + ) || ( - moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD").subtract(1, "year") <= currDate + moment(dateWithoutYear, "MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD").subtract(1, "year") <= currDate ) ) ) { validity = true; } } - + if (dates[i].Geburtstag) { const date = dates[i].Geburtstag; const dateWithoutYear = moment(date).format("MM-DD"); if ( - date && + date && ( ( - moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD") <= currDate - ) + moment(dateWithoutYear, "MM-DD") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD") <= currDate + ) || ( - moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD").subtract(1, "year") <= currDate + moment(dateWithoutYear, "MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD").subtract(1, "year") <= currDate ) ) ) { @@ -277,36 +403,36 @@ app.get('/api/toshow/:targetDate?', async (req, res) => { } } - + if (!validity) delete dates[i]; } dates = dates.filter((date) => date !== null); - - for(let i = 14; i >= 1; i--) { + + for (let i = 14; i >= 1; i--) { console.log(i); const date = moment(currDate).subtract(i, "days") const dayOfWeek = date.format("dddd"); - if(["Saturday", "Sunday"].includes(dayOfWeek)) continue; + if (["Saturday", "Sunday"].includes(dayOfWeek)) continue; - for(let j in dates) { + for (let j in dates) { let validity = false; if (dates[j].Anstelldatum) { const dateWithoutYear = moment(dates[j].Anstelldatum, "YYYY-MM-DD").format("MM-DD"); if ( - date && + date && ( ( - moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD") <= moment(currDate).subtract(i, "days") - ) + moment(dateWithoutYear, "MM-DD") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD") <= moment(currDate).subtract(i, "days") + ) || ( - moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD").subtract(1, "year") <= moment(currDate).subtract(i, "days") + moment(dateWithoutYear, "MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD").subtract(1, "year") <= moment(currDate).subtract(i, "days") ) ) ) { @@ -318,16 +444,16 @@ app.get('/api/toshow/:targetDate?', async (req, res) => { const dateWithoutYear = moment(dates[j].Geburtstag, "YYYY-MM-DD").format("MM-DD"); if ( - date && + date && ( ( - moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD") <= moment(currDate).subtract(i, "days") - ) + moment(dateWithoutYear, "MM-DD") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD") <= moment(currDate).subtract(i, "days") + ) || ( - moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && - moment(dateWithoutYear,"MM-DD").subtract(1, "year") <= moment(currDate).subtract(i, "days") + moment(dateWithoutYear, "MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && + moment(dateWithoutYear, "MM-DD").subtract(1, "year") <= moment(currDate).subtract(i, "days") ) ) ) { @@ -337,9 +463,26 @@ app.get('/api/toshow/:targetDate?', async (req, res) => { console.log(date); - console.log(date.format("dddd"), dates[j].Vorname, dates[j].Nachname, validity); - if (validity) delete dates[j]; + if (validity) { + delete dates[j]; + continue; + } + + // get the EventType depending on whats closer to the current date + const dateWithoutYear = moment(dates[j].Anstelldatum, "YYYY-MM-DD").format("MM-DD"); + const dateWithoutYear2 = moment(dates[j].Geburtstag, "YYYY-MM-DD").format("MM-DD"); + + if (dates[j].Anstelldatum && !dates[j].Geburtstag) dates[j].EventType = "Jubiläum"; + else if (dates[j].Geburtstag && !dates[j].Anstelldatum) dates[j].EventType = "Geburtstag"; + else if ( + Math.abs(moment(dateWithoutYear, "MM-DD").diff(moment(currDate), "days")) <= + Math.abs(moment(dateWithoutYear2, "MM-DD").diff(moment(currDate), "days")) + ) dates[j].EventType = "Jubiläum" + else dates[j].EventType = "Geburtstag" + + + console.log(date.format("dddd"), dates[j].Vorname, dates[j].Nachname, validity, dates[j].EventType); } dates = dates.filter((date) => date !== null); } diff --git a/backend/src/types.d.ts b/backend/src/types.d.ts index 5dcba8c..08d210d 100644 --- a/backend/src/types.d.ts +++ b/backend/src/types.d.ts @@ -5,11 +5,15 @@ declare namespace Types { message: string } - interface Mitarbeiter { + interface Employees { ID: string; Vorname: string; Nachname: string; Anstelldatum: Date | null; Geburtstag: Date | null; } + + interface EmployeeEvent extends Employees { + EventType?: "Geburtstag" | "Jubiläum"; + } } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 73b848d..cf4fc3f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,12 +16,14 @@ "@mui/lab": "^5.0.0-alpha.154", "@mui/material": "^5.14.10", "@mui/x-data-grid": "^6.19.1", + "@mui/x-date-pickers": "^6.19.6", "@types/node": "^16.18.52", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", "axios": "^1.5.1", "js-sha256": "^0.10.1", "moment": "^2.29.4", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-calendar": "^4.8.0", "react-dom": "^18.2.0", @@ -32,6 +34,9 @@ "suneditor-react": "^3.6.1", "typescript": "^4.9.5", "zustand": "^4.4.4" + }, + "devDependencies": { + "@types/papaparse": "^5.3.14" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1951,9 +1956,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2528,28 +2533,28 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", - "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "dependencies": { - "@floating-ui/utils": "^0.1.3" + "@floating-ui/utils": "^0.2.1" } }, "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", - "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.5.1" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -2557,9 +2562,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.4.tgz", - "integrity": "sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, "node_modules/@fontsource-variable/lexend": { "version": "5.0.12", @@ -3603,9 +3608,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.10", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.10.tgz", - "integrity": "sha512-wX1vbDC+lzF7FlhT6A3ffRZgEoKWPF8VqRoTu4lZwouFX2t90KyCMsgepMw5DxLak1BSp/KP86CmtZttikb/gQ==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", + "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0" }, @@ -3616,11 +3621,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.19", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.19.tgz", - "integrity": "sha512-qAHvTXzk7basbyqPvhgWqN6JbmI2wLB/mf97GkSlz5c76MiKYV6Ffjvw9BjKZQ1YRb8rDX9kgdjRezOcoB91oQ==", + "version": "5.15.12", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.12.tgz", + "integrity": "sha512-8SDGCnO2DY9Yy+5bGzu00NZowSDtuyHP4H8gunhHGQoIlhlY2Z3w64wBzAOLpYw/ZhJNzksDTnS/i8qdJvxuow==", "dependencies": { - "@babel/runtime": "^7.23.4", + "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -3672,6 +3677,102 @@ "react-dom": "^17.0.0 || ^18.0.0" } }, + "node_modules/@mui/x-date-pickers": { + "version": "6.19.6", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.19.6.tgz", + "integrity": "sha512-QW9AFcPi0vLpkUhmquhhyhLaBvB0AZJuu3NTrE173qNKx3Z3n51aCLY9bc7c6i4ltZMMsVRHlvzQjsve04TC8A==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "date-fns": "^2.25.0 || ^3.2.0", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/base": { + "version": "5.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.38.tgz", + "integrity": "sha512-AsjD6Y1X5A1qndxz8xCcR8LDqv31aiwlgWMPxFAX/kCKiIGKlK65yMeVZ62iQr/6LBz+9hSKLiD1i4TZdAHKcQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.12", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -4328,6 +4429,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.52.tgz", "integrity": "sha512-sm2aph6cRSsTMFYFgI+RpPLunXO9ClJkpizUVdT7KmGeyfQ14xnjTMT/f3MHcfKqevXqGT6BgVFzW8wcEoDUtA==" }, + "node_modules/@types/papaparse": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.14.tgz", + "integrity": "sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4377,9 +4487,9 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "dependencies": { "@types/react": "*" } @@ -6042,9 +6152,9 @@ } }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", "engines": { "node": ">=6" } @@ -12842,6 +12952,11 @@ "node": ">=6" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 01da27d..58823f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,12 +11,14 @@ "@mui/lab": "^5.0.0-alpha.154", "@mui/material": "^5.14.10", "@mui/x-data-grid": "^6.19.1", + "@mui/x-date-pickers": "^6.19.6", "@types/node": "^16.18.52", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", "axios": "^1.5.1", "js-sha256": "^0.10.1", "moment": "^2.29.4", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-calendar": "^4.8.0", "react-dom": "^18.2.0", @@ -50,5 +52,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/papaparse": "^5.3.14" } } diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 194f79a..fcd36cd 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -46,8 +46,8 @@ function Sidebar() { gap: "2px", }}> - - + + {/* */} (""); const [password, setPassword] = useState(""); - const [permissions, setPermissions] = useState({ - admin: false, - article_create: false, - article_manage: false, - sponsor_manage: false, - user_manage: false, - }); - const valid = () => { if (!username) return false; if (!password && id === "create") return false; @@ -47,13 +39,6 @@ function UserModal({ useEffect(() => { setUsername(""); setPassword(""); - setPermissions({ - admin: false, - article_create: false, - article_manage: false, - sponsor_manage: false, - user_manage: false, - }); if (!id) return; if (id === "create") return; @@ -67,15 +52,6 @@ function UserModal({ }) .then((res) => { setUsername(res.data.username); - - setPermissions({ - admin: res.data.admin, - article_create: res.data.article_create, - article_manage: res.data.article_manage, - sponsor_manage: res.data.sponsor_manage, - user_manage: res.data.user_manage, - }); - setLoading(false); }); }, [id]); @@ -186,112 +162,6 @@ function UserModal({ }} /> - - - { - setPermissions({ - ...permissions, - admin: e.target.checked, - }); - }} - /> - Admin - - - - { - setPermissions({ - ...permissions, - article_create: e.target.checked, - }); - }} - /> - Artikel Erstellen - - - - { - setPermissions({ - ...permissions, - article_manage: e.target.checked, - }); - }} - /> - Artikel Verwalten - - - - { - setPermissions({ - ...permissions, - sponsor_manage: e.target.checked, - }); - }} - /> - Sponsoren Verwalten - - - - { - setPermissions({ - ...permissions, - user_manage: e.target.checked, - }); - }} - /> - Nutzer Verwalten - - - + + ); \ No newline at end of file diff --git a/frontend/src/pages/AdminFrame.tsx b/frontend/src/pages/AdminFrame.tsx index 971534b..d975bc6 100644 --- a/frontend/src/pages/AdminFrame.tsx +++ b/frontend/src/pages/AdminFrame.tsx @@ -4,8 +4,8 @@ import Sidebar from "../components/Sidebar"; import Dashboard from "./admin/Dashboard"; import Benutzer from "./admin/Benutzer"; import Logout from "./admin/Logout"; -import Import from "./admin/Import"; import Kalender from "./admin/Kalender"; +import ManageData from "./admin/ManageData"; function AdminFrame() { return ( @@ -31,7 +31,7 @@ function AdminFrame() { } /> } /> } /> - } /> + } /> } /> diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index f00637c..c8ecdbc 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1,21 +1,52 @@ -import { Box, Typography } from "@mui/material"; +import { Box, Collapse, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import PhilSwift from "../components/PhilSwift"; import axios from "axios"; import { getBaseURL } from "../functions"; -import CompareDate from "../components/CompareDate"; +import moment from "moment"; + +interface EmployeeWithColor extends Types.Employees { + color: string; + EventType: "Geburtstag" | "Jubiläum"; +} function LandingPage() { - const [isHalloween, setIsHalloween] = useState(false); + const [data, setData] = useState([]); + const [currEvent, setCurrEvent] = useState(0); useEffect(() => { - const currentDate = new Date(); - const halloweenDate = new Date("2024-03-04"); + axios + .get(`${getBaseURL()}/api/toshow`) + .then((res) => { + setData(res.data.map((employee: Types.Employees) => ({ + ...employee, + color: `hsl(${Math.random() * 360}, 100%, 50%)`, + }))); + }) + .catch((err) => { + console.error(err); + }); + }, []); - setIsHalloween( - currentDate.getMonth() === halloweenDate.getMonth() && - currentDate.getDate() === halloweenDate.getDate() - ); + useEffect(() => { + const interval = setInterval(() => { + setCurrEvent((prev) => { + if (data.length === 0) return 0; + if (data.length === 1) return 0; + if (prev === data.length - 1) return 0; + else return prev + 1; + }); + }, 5000); + + return () => clearInterval(interval); + }, [data]); + + useEffect(() => { + const interval = setInterval(() => { + window.location.reload(); + }, moment().endOf("day").diff(moment(), "milliseconds")); + + return () => clearInterval(interval); }, []); return ( @@ -32,7 +63,7 @@ function LandingPage() { alignItems: "center", }} > - + {/* */} - - {isHalloween - ? "Happy Halloween" - : "Keine Geburtstage oder Jubiläen heute."} - + {data.map((employee, index) => { + return ( + + + + {employee.Vorname} {employee.Nachname} + + + {employee.EventType === "Geburtstag" ? "hat Geburtstag" : "hat Jubiläum"} + + + + ); + })} ); diff --git a/frontend/src/pages/admin/Benutzer.tsx b/frontend/src/pages/admin/Benutzer.tsx index 900bced..95dc395 100644 --- a/frontend/src/pages/admin/Benutzer.tsx +++ b/frontend/src/pages/admin/Benutzer.tsx @@ -112,7 +112,6 @@ function Benutzer() { Name - Berechtigungen @@ -120,18 +119,6 @@ function Benutzer() { {users.map((user) => ( {user.username} - - {[ - { name: "Admin", value: user.admin }, - { name: "Artikel Erstellen", value: user.article_create }, - { name: "Artikel Verwalten", value: user.article_manage }, - { name: "Sponsoren Verwalten", value: user.sponsor_manage }, - { name: "Nutzer Verwalten", value: user.user_manage }, - ] - .filter((e) => e.value) - .map((e) => e.name) - .join(", ")} - moment(params.value).format("DD.MM.YYYY") - - }, - { - field: "Geburtstag", - headerName: "Geburtstag", - type: "date", - width: 160, - valueFormatter: params => moment(params.value).format("DD.MM.YYYY") - - }, -]; +import { Box, Typography } from '@mui/material' +import React from 'react' function Dashboard() { - const [workers, setWorkers] = useState([]); - - useEffect(() => { - axios - .get(`${getBaseURL()}/api/workers`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - }) - .then((res) => { - setWorkers(res.data); - }) - .catch((err) => { - console.log(err); - }); - }, []); - - const rows = workers.map((worker, index) => ({ - id: index, - Vorname: worker.Vorname, - Nachname: worker.Nachname, - Geburtstag: worker.Geburtstag, - Anstelldatum: worker.Anstelldatum, - })) - return ( - - - Nächste Geburtstage - - - + + Noch nicht Implementiert! - ); + ) } -export default Dashboard; +export default Dashboard \ No newline at end of file diff --git a/frontend/src/pages/admin/ManageData.tsx b/frontend/src/pages/admin/ManageData.tsx new file mode 100644 index 0000000..b140bca --- /dev/null +++ b/frontend/src/pages/admin/ManageData.tsx @@ -0,0 +1,899 @@ +import { + Alert, + Backdrop, + Box, + Button, + Checkbox, + CircularProgress, + Collapse, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import axios from "axios"; +import { useEffect, useState } from "react"; +import { getBaseURL } from "../../functions"; +import moment from "moment"; +import { Add, Delete, Edit, Refresh, UploadFile } from "@mui/icons-material"; +import { DatePicker } from "@mui/x-date-pickers"; +import PapaParse from "papaparse"; + +function ManageData() { + const [employees, setEmployees] = useState([]); + const [checked, setChecked] = useState([]); + + const [loading, setLoading] = useState(false); + + const [createPopup, setCreatePopup] = useState(false); + const [editPopup, setEditPopup] = useState(false); + const [deletePopup, setDeletePopup] = useState( + false + ); + const [uploadPopup, setUploadPopup] = useState(false); + + const getEmployees = () => { + setLoading(true); + setChecked([]); + axios + .get(`${getBaseURL()}/api/employees`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }) + .then((res) => { + setEmployees(res.data); + setLoading(false); + }) + .catch((err) => { + console.log(err); + }); + }; + + useEffect(() => { + getEmployees(); + }, []); + + useEffect(() => { + if (!createPopup) getEmployees(); + }, [createPopup]); + + useEffect(() => { + if (!editPopup) getEmployees(); + }, [editPopup]); + + useEffect(() => { + if (!deletePopup) getEmployees(); + }, [deletePopup]); + + useEffect(() => { + if (!uploadPopup) getEmployees(); + }, [uploadPopup]); + + return ( + + + + + + {CreatePopUp(createPopup, setCreatePopup)} + {EditPopUp(editPopup, setEditPopup)} + {DeletePopUp(deletePopup, setDeletePopup)} + {UploadPopUp(uploadPopup, setUploadPopup)} + + + Daten Verwalten + + + + + + + + + + + + + + + + 0}> + + + + + + + + + + + { + if (v) setChecked(employees.map((employee) => employee.ID)); + else setChecked([]); + }} + /> + + Nachname + Vorname + Geburtstag + Anstelldatum + + + + + {employees.map((employee, index) => ( + + + { + if (v) setChecked([...checked, employee.ID]); + else setChecked(checked.filter((id) => id !== employee.ID)); + }} + /> + + {employee.Nachname} + {employee.Vorname} + + {employee.Geburtstag + ? moment(employee.Geburtstag).format("DD.MM.YYYY") + : "-"} + + + {employee.Anstelldatum + ? moment(employee.Anstelldatum).format("DD.MM.YYYY") + : "-"} + + + + + + + + + ))} + +
+
+ ); +} + +export default ManageData; + +function CreatePopUp( + createPopup: boolean, + setCreatePopup: (value: boolean) => void +) { + const [vorname, setVorname] = useState(""); + const [nachname, setNachname] = useState(""); + + const [showGeburtstag, setShowGeburtstag] = useState(false); + const [geburtstag, setGeburtstag] = useState(null); + const [showAnstelldatum, setShowAnstelldatum] = useState(false); + const [anstelldatum, setAnstelldatum] = useState(null); + + const [loading, setLoading] = useState(false); + + const checkValid = () => { + if (vorname === "" || vorname.length > 24) return false; + if (nachname === "" || nachname.length > 24) return false; + + if (showGeburtstag && !geburtstag) return false; + if (showAnstelldatum && !anstelldatum) return false; + + return true; + }; + + useEffect(() => { + if (!createPopup) { + setVorname(""); + setNachname(""); + setShowGeburtstag(false); + setGeburtstag(null); + setShowAnstelldatum(false); + setAnstelldatum(null); + setLoading(false); + } + }, [createPopup]); + + return ( + + + + Neuen Mitarbeiter Hinzufügen + + + { + if (e.target.value.length > 24) return; + setVorname(e.target.value); + }} + /> + { + if (e.target.value.length > 24) return; + setNachname(e.target.value); + }} + /> + + + setShowGeburtstag(v)} + /> + setGeburtstag(v)} + disabled={!showGeburtstag} + maxDate={moment() as any} + /> + + + + setShowAnstelldatum(v)} + /> + setAnstelldatum(v)} + disabled={!showAnstelldatum} + maxDate={moment() as any} + /> + + + + + + + + + ); +} + +function EditPopUp( + editPopup: Types.Employees | false, + setEditPopup: (value: Types.Employees | false) => void +) { + const [vorname, setVorname] = useState( + editPopup ? editPopup.Vorname : "" + ); + const [nachname, setNachname] = useState( + editPopup ? editPopup.Nachname : "" + ); + + const [showGeburtstag, setShowGeburtstag] = useState( + editPopup ? !!editPopup.Geburtstag : false + ); + const [geburtstag, setGeburtstag] = useState( + editPopup + ? editPopup.Geburtstag + ? moment(editPopup.Geburtstag) + : null + : null + ); + const [showAnstelldatum, setShowAnstelldatum] = useState( + editPopup ? !!editPopup.Anstelldatum : false + ); + const [anstelldatum, setAnstelldatum] = useState( + editPopup + ? editPopup.Anstelldatum + ? moment(editPopup.Anstelldatum) + : null + : null + ); + + const [loading, setLoading] = useState(false); + + const checkValid = () => { + if (vorname === "" || vorname.length > 24) return false; + if (nachname === "" || nachname.length > 24) return false; + + if (showGeburtstag && !geburtstag) return false; + if (showAnstelldatum && !anstelldatum) return false; + + return true; + }; + + useEffect(() => { + if (editPopup) { + setVorname(editPopup.Vorname); + setNachname(editPopup.Nachname); + setShowGeburtstag(!!editPopup.Geburtstag); + setGeburtstag(editPopup.Geburtstag ? moment(editPopup.Geburtstag) : null); + setShowAnstelldatum(!!editPopup.Anstelldatum); + setAnstelldatum( + editPopup.Anstelldatum ? moment(editPopup.Anstelldatum) : null + ); + } else { + setVorname(""); + setNachname(""); + setShowGeburtstag(false); + setGeburtstag(null); + setShowAnstelldatum(false); + setAnstelldatum(null); + setLoading(false); + } + }, [editPopup]); + + return ( + + + + {editPopup && editPopup.Vorname[0]} + {". "} {editPopup && editPopup.Nachname} Bearbeiten + + + { + if (e.target.value.length > 24) return; + setVorname(e.target.value); + }} + /> + { + if (e.target.value.length > 24) return; + setNachname(e.target.value); + }} + /> + + + setShowGeburtstag(v)} + /> + setGeburtstag(v)} + disabled={!showGeburtstag} + maxDate={moment() as any} + /> + + + + setShowAnstelldatum(v)} + /> + setAnstelldatum(v)} + disabled={!showAnstelldatum} + maxDate={moment() as any} + /> + + + + + + + + + ); +} + +function DeletePopUp( + deletePopup: Types.Employees | false, + setDeletePopup: (value: Types.Employees | false) => void +) { + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!deletePopup) setLoading(false); + }, [deletePopup]); + + if (deletePopup === false) return <>; + + return ( + + + + Mitarbeiter Löschen + + + + Wollen Sie wirklich den Mitarbeiter{" "} + + {deletePopup?.Vorname} {deletePopup?.Nachname}{" "} + + löschen? + + + + + + + + + ); +} + +interface EmployeePartial { + Vorname: string; + Nachname: string; + Geburtstag: moment.Moment | null; + Anstelldatum: moment.Moment | null; +} + +function UploadPopUp( + uploadPopup: boolean, + setUploadPopup: (value: boolean) => void +) { + const [loading, setLoading] = useState(false); + + const [data, setData] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!uploadPopup) setLoading(false); + }, [uploadPopup]); + + return ( + + + + CSV Hochladen + + + + {error} + + + { + if (!e.target.files) return; + + const file = e.target.files[0]; + + const reader = new FileReader(); + reader.onload = (e) => { + const result = e.target?.result as string; + + const data: PapaParse.ParseResult = PapaParse.parse(result) + + if((data.data[0]).length !== 4) return setError("CSV Datei ist nicht im richtigen Format"); + if(data.data[0][0] !== "Vorname") return setError("CSV Datei ist nicht im richtigen Format"); + if(data.data[0][1] !== "Nachname") return setError("CSV Datei ist nicht im richtigen Format"); + if(data.data[0][2] !== "Geburtstag") return setError("CSV Datei ist nicht im richtigen Format"); + if(data.data[0][3] !== "Anstelldatum") return setError("CSV Datei ist nicht im richtigen Format"); + + const employees: EmployeePartial[] = []; + for(let i = 1; i < data.data.length; i++) { + const employee = data.data[i]; + if(employee.length !== 4) return setError("CSV Datei ist nicht im richtigen Format"); + if(employee[0].length > 24) return setError("Vorname ist zu lang"); + if(employee[1].length > 24) return setError("Nachname ist zu lang"); + if(employee[2] !== "" && !moment(employee[2], "DD.MM.YYYY", true).isValid()) return setError("Geburtstag ist nicht im richtigen Format"); + if(employee[3] !== "" && !moment(employee[3], "DD.MM.YYYY", true).isValid()) return setError("Anstelldatum ist nicht im richtigen Format"); + + employees.push({ + Vorname: employee[0], + Nachname: employee[1], + Geburtstag: employee[2] === "" ? null : moment(employee[2], "DD.MM.YYYY"), + Anstelldatum: employee[3] === "" ? null : moment(employee[3], "DD.MM.YYYY"), + }); + } + + setData(employees); + }; + + reader.readAsText(file); + }} + /> + + + + + + + + ); +} diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index 2561fdc..288cea7 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -1,9 +1,10 @@ declare namespace Types { - interface Worker { + interface Employees { + ID: string; Vorname: string; Nachname: string; - Geburtstag: Date; - Anstelldatum: Date; + Geburtstag?: Date; + Anstelldatum?: Date; } interface User extends UserPermissions { ID: string; @@ -17,12 +18,4 @@ declare namespace Types { sponsor_manage: boolean; user_manage: boolean; } - - interface Sponsor { - ID: string; - name: string; - url: string; - description: string; - addedAt: Date; - } } \ No newline at end of file