This commit is contained in:
Ipmake 2024-03-11 23:52:37 +01:00
parent 57a1a0105f
commit 64ea467e3c
15 changed files with 1378 additions and 416 deletions

View File

@ -9,14 +9,14 @@ datasource db {
extensions = [uuid_ossp(map: "uuid-ossp")] extensions = [uuid_ossp(map: "uuid-ossp")]
} }
model users { model Users {
ID String @id @default(dbgenerated("uuid_generate_v1()")) @db.Uuid ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
username String username String
password String @db.VarChar(128) password String @db.VarChar(128)
token String @db.VarChar(128) token String @db.VarChar(128)
} }
model Mitarbeiter { model Employees {
ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
Vorname String Vorname String
Nachname String Nachname String

View File

@ -62,7 +62,7 @@ app.get('/api/auth/verify', async (req, res) => {
res.send('OK') 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] const token = req.headers.authorization?.split(' ')[1]
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) 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) const user = await authorize(token)
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
const { title, content, image, sponsors } = req.body if (!Array.isArray(req.body)) return res.status(400).send(Errors.INVALID_ITEMS)
if (!title || !content || !image) return res.status(400).send(Errors.MISSING_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] const token = req.headers.authorization?.split(' ')[1]
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS) 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) const user = await authorize(token)
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS) 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: { select: {
ID: true,
Vorname: true, Vorname: true,
Nachname: true, Nachname: true,
Geburtstag: true, Geburtstag: true,
Anstelldatum: true, Anstelldatum: true,
},
orderBy: {
Nachname: 'asc'
} }
}) })
res.send(workers) 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) => { app.get('/api/users', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1] 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) if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
const { username, password, admin, article_create, article_manage, sponsor_manage, user_manage } = req.body const { username, password } = 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) if (!username) return res.status(400).send(Errors.MISSING_ITEMS)
const newUser = await prisma.users.update({ const newUser = await prisma.users.update({
where: { where: {
@ -181,11 +321,6 @@ app.patch('/api/users/edit/:id', async (req, res) => {
}, },
data: { data: {
username, username,
admin,
article_create,
article_manage,
sponsor_manage,
user_manage,
...(password && { password }) ...(password && { password })
} }
}) })
@ -211,26 +346,17 @@ app.delete('/api/users/delete/:id', async (req, res) => {
app.get('/api/toshow/:targetDate?', async (req, res) => { app.get('/api/toshow/:targetDate?', async (req, res) => {
const { targetDate } = req.params; const { targetDate } = req.params;
if(targetDate && targetDate.length !== 10) return res.status(400).send(Errors.INVALID_ITEMS) if (targetDate && targetDate.length !== 10) return res.status(400).send(Errors.INVALID_ITEMS)
const token = req.headers.authorization?.split(' ')[1]
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; let currDate;
if(targetDate) currDate = moment(targetDate, "YYYY-MM-DD"); if (targetDate) currDate = moment(targetDate, "YYYY-MM-DD");
else currDate = moment(); else currDate = moment();
for (let i in dates) { for (let i in dates) {
let validity = false; let validity = false;
// `${currDate.getFullYear()}-${getFormatNumber(date.getMonth(), 2)}-${getFormatNumber(date.getDay(), 2)}` // `${currDate.getFullYear()}-${getFormatNumber(date.getMonth(), 2)}-${getFormatNumber(date.getDay(), 2)}`
if (dates[i].Anstelldatum) { if (dates[i].Anstelldatum) {
@ -241,13 +367,13 @@ app.get('/api/toshow/:targetDate?', async (req, res) => {
date && date &&
( (
( (
moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && moment(dateWithoutYear, "MM-DD") >= moment(currDate).subtract(14, "days") &&
moment(dateWithoutYear,"MM-DD") <= currDate moment(dateWithoutYear, "MM-DD") <= currDate
) )
|| ||
( (
moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && 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") <= currDate
) )
) )
) { ) {
@ -263,13 +389,13 @@ app.get('/api/toshow/:targetDate?', async (req, res) => {
date && date &&
( (
( (
moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && moment(dateWithoutYear, "MM-DD") >= moment(currDate).subtract(14, "days") &&
moment(dateWithoutYear,"MM-DD") <= currDate moment(dateWithoutYear, "MM-DD") <= currDate
) )
|| ||
( (
moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "days") && 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") <= currDate
) )
) )
) { ) {
@ -283,14 +409,14 @@ app.get('/api/toshow/:targetDate?', async (req, res) => {
dates = dates.filter((date) => date !== null); dates = dates.filter((date) => date !== null);
for(let i = 14; i >= 1; i--) { for (let i = 14; i >= 1; i--) {
console.log(i); console.log(i);
const date = moment(currDate).subtract(i, "days") const date = moment(currDate).subtract(i, "days")
const dayOfWeek = date.format("dddd"); 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; let validity = false;
if (dates[j].Anstelldatum) { if (dates[j].Anstelldatum) {
@ -300,13 +426,13 @@ app.get('/api/toshow/:targetDate?', async (req, res) => {
date && date &&
( (
( (
moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && 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(i, "days")
) )
|| ||
( (
moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "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(i, "days")
) )
) )
) { ) {
@ -321,13 +447,13 @@ app.get('/api/toshow/:targetDate?', async (req, res) => {
date && date &&
( (
( (
moment(dateWithoutYear,"MM-DD") >= moment(currDate).subtract(14, "days") && 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(i, "days")
) )
|| ||
( (
moment(dateWithoutYear,"MM-DD").subtract(1, "year") >= moment(currDate).subtract(14, "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(i, "days")
) )
) )
) { ) {
@ -337,9 +463,26 @@ app.get('/api/toshow/:targetDate?', async (req, res) => {
console.log(date); 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); dates = dates.filter((date) => date !== null);
} }

View File

@ -5,11 +5,15 @@ declare namespace Types {
message: string message: string
} }
interface Mitarbeiter { interface Employees {
ID: string; ID: string;
Vorname: string; Vorname: string;
Nachname: string; Nachname: string;
Anstelldatum: Date | null; Anstelldatum: Date | null;
Geburtstag: Date | null; Geburtstag: Date | null;
} }
interface EmployeeEvent extends Employees {
EventType?: "Geburtstag" | "Jubiläum";
}
} }

View File

@ -16,12 +16,14 @@
"@mui/lab": "^5.0.0-alpha.154", "@mui/lab": "^5.0.0-alpha.154",
"@mui/material": "^5.14.10", "@mui/material": "^5.14.10",
"@mui/x-data-grid": "^6.19.1", "@mui/x-data-grid": "^6.19.1",
"@mui/x-date-pickers": "^6.19.6",
"@types/node": "^16.18.52", "@types/node": "^16.18.52",
"@types/react": "^18.2.22", "@types/react": "^18.2.22",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"axios": "^1.5.1", "axios": "^1.5.1",
"js-sha256": "^0.10.1", "js-sha256": "^0.10.1",
"moment": "^2.29.4", "moment": "^2.29.4",
"papaparse": "^5.4.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-calendar": "^4.8.0", "react-calendar": "^4.8.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -32,6 +34,9 @@
"suneditor-react": "^3.6.1", "suneditor-react": "^3.6.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"zustand": "^4.4.4" "zustand": "^4.4.4"
},
"devDependencies": {
"@types/papaparse": "^5.3.14"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -1951,9 +1956,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.23.5", "version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz",
"integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@ -2528,28 +2533,28 @@
} }
}, },
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.5.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
"dependencies": { "dependencies": {
"@floating-ui/utils": "^0.1.3" "@floating-ui/utils": "^0.2.1"
} }
}, },
"node_modules/@floating-ui/dom": { "node_modules/@floating-ui/dom": {
"version": "1.5.3", "version": "1.6.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
"dependencies": { "dependencies": {
"@floating-ui/core": "^1.4.2", "@floating-ui/core": "^1.0.0",
"@floating-ui/utils": "^0.1.3" "@floating-ui/utils": "^0.2.0"
} }
}, },
"node_modules/@floating-ui/react-dom": { "node_modules/@floating-ui/react-dom": {
"version": "2.0.4", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
"integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.5.1" "@floating-ui/dom": "^1.6.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=16.8.0", "react": ">=16.8.0",
@ -2557,9 +2562,9 @@
} }
}, },
"node_modules/@floating-ui/utils": { "node_modules/@floating-ui/utils": {
"version": "0.1.4", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.4.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
"integrity": "sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==" "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
}, },
"node_modules/@fontsource-variable/lexend": { "node_modules/@fontsource-variable/lexend": {
"version": "5.0.12", "version": "5.0.12",
@ -3603,9 +3608,9 @@
} }
}, },
"node_modules/@mui/types": { "node_modules/@mui/types": {
"version": "7.2.10", "version": "7.2.13",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.10.tgz", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz",
"integrity": "sha512-wX1vbDC+lzF7FlhT6A3ffRZgEoKWPF8VqRoTu4lZwouFX2t90KyCMsgepMw5DxLak1BSp/KP86CmtZttikb/gQ==", "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==",
"peerDependencies": { "peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0" "@types/react": "^17.0.0 || ^18.0.0"
}, },
@ -3616,11 +3621,11 @@
} }
}, },
"node_modules/@mui/utils": { "node_modules/@mui/utils": {
"version": "5.14.19", "version": "5.15.12",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.19.tgz", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.12.tgz",
"integrity": "sha512-qAHvTXzk7basbyqPvhgWqN6JbmI2wLB/mf97GkSlz5c76MiKYV6Ffjvw9BjKZQ1YRb8rDX9kgdjRezOcoB91oQ==", "integrity": "sha512-8SDGCnO2DY9Yy+5bGzu00NZowSDtuyHP4H8gunhHGQoIlhlY2Z3w64wBzAOLpYw/ZhJNzksDTnS/i8qdJvxuow==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.23.4", "@babel/runtime": "^7.23.9",
"@types/prop-types": "^15.7.11", "@types/prop-types": "^15.7.11",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-is": "^18.2.0" "react-is": "^18.2.0"
@ -3672,6 +3677,102 @@
"react-dom": "^17.0.0 || ^18.0.0" "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": { "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1", "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", "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", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.52.tgz",
"integrity": "sha512-sm2aph6cRSsTMFYFgI+RpPLunXO9ClJkpizUVdT7KmGeyfQ14xnjTMT/f3MHcfKqevXqGT6BgVFzW8wcEoDUtA==" "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": { "node_modules/@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@ -4377,9 +4487,9 @@
} }
}, },
"node_modules/@types/react-transition-group": { "node_modules/@types/react-transition-group": {
"version": "4.4.6", "version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
"integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
} }
@ -6042,9 +6152,9 @@
} }
}, },
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.0.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -12842,6 +12952,11 @@
"node": ">=6" "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": { "node_modules/param-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",

View File

@ -11,12 +11,14 @@
"@mui/lab": "^5.0.0-alpha.154", "@mui/lab": "^5.0.0-alpha.154",
"@mui/material": "^5.14.10", "@mui/material": "^5.14.10",
"@mui/x-data-grid": "^6.19.1", "@mui/x-data-grid": "^6.19.1",
"@mui/x-date-pickers": "^6.19.6",
"@types/node": "^16.18.52", "@types/node": "^16.18.52",
"@types/react": "^18.2.22", "@types/react": "^18.2.22",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"axios": "^1.5.1", "axios": "^1.5.1",
"js-sha256": "^0.10.1", "js-sha256": "^0.10.1",
"moment": "^2.29.4", "moment": "^2.29.4",
"papaparse": "^5.4.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-calendar": "^4.8.0", "react-calendar": "^4.8.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -50,5 +52,8 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"@types/papaparse": "^5.3.14"
} }
} }

View File

@ -46,8 +46,8 @@ function Sidebar() {
gap: "2px", gap: "2px",
}}> }}>
<SidebarElement Title="Dashboard" Icon={Home} Path="/admin/dashboard" /> <SidebarElement Title="Dashboard" Icon={Home} Path="/admin/dashboard" />
<SidebarElement Title="Daten import" Icon={TableChartOutlined} Path="/admin/import" /> <SidebarElement Title="Verwalten" Icon={TableChartOutlined} Path="/admin/manage" />
<SidebarElement Title="Kalender" Icon={Event} Path="/admin/Kalender" /> {/* <SidebarElement Title="Kalender" Icon={Event} Path="/admin/Kalender" /> */}
<SidebarElement Title="Benutzer" Icon={Person} Path="/admin/benutzer" /> <SidebarElement Title="Benutzer" Icon={Person} Path="/admin/benutzer" />
<SidebarElement Title="Zur Website" Icon={ArrowBack} Path="/" sx={{ <SidebarElement Title="Zur Website" Icon={ArrowBack} Path="/" sx={{

View File

@ -29,14 +29,6 @@ function UserModal({
const [username, setUsername] = useState<string>(""); const [username, setUsername] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [permissions, setPermissions] = useState<Types.UserPermissions>({
admin: false,
article_create: false,
article_manage: false,
sponsor_manage: false,
user_manage: false,
});
const valid = () => { const valid = () => {
if (!username) return false; if (!username) return false;
if (!password && id === "create") return false; if (!password && id === "create") return false;
@ -47,13 +39,6 @@ function UserModal({
useEffect(() => { useEffect(() => {
setUsername(""); setUsername("");
setPassword(""); setPassword("");
setPermissions({
admin: false,
article_create: false,
article_manage: false,
sponsor_manage: false,
user_manage: false,
});
if (!id) return; if (!id) return;
if (id === "create") return; if (id === "create") return;
@ -67,15 +52,6 @@ function UserModal({
}) })
.then((res) => { .then((res) => {
setUsername(res.data.username); 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); setLoading(false);
}); });
}, [id]); }, [id]);
@ -186,112 +162,6 @@ function UserModal({
}} }}
/> />
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "flex-start",
width: "80%",
mx: "20px",
}}
>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
>
<Checkbox
checked={permissions.admin}
onChange={(e) => {
setPermissions({
...permissions,
admin: e.target.checked,
});
}}
/>
Admin
</Stack>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
>
<Checkbox
disabled={permissions.admin}
checked={permissions.article_create}
onChange={(e) => {
setPermissions({
...permissions,
article_create: e.target.checked,
});
}}
/>
Artikel Erstellen
</Stack>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
>
<Checkbox
disabled={permissions.admin}
checked={permissions.article_manage}
onChange={(e) => {
setPermissions({
...permissions,
article_manage: e.target.checked,
});
}}
/>
Artikel Verwalten
</Stack>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
>
<Checkbox
disabled={permissions.admin}
checked={permissions.sponsor_manage}
onChange={(e) => {
setPermissions({
...permissions,
sponsor_manage: e.target.checked,
});
}}
/>
Sponsoren Verwalten
</Stack>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
>
<Checkbox
disabled={permissions.admin}
checked={permissions.user_manage}
onChange={(e) => {
setPermissions({
...permissions,
user_manage: e.target.checked,
});
}}
/>
Nutzer Verwalten
</Stack>
</Box>
<LoadingButton <LoadingButton
variant="contained" variant="contained"
sx={{ width: "80%" }} sx={{ width: "80%" }}
@ -305,20 +175,7 @@ function UserModal({
`${getBaseURL()}/api/users/create`, `${getBaseURL()}/api/users/create`,
{ {
username, username,
password: sha256(`rheine ${password.trim()} rheine`), password: sha256(`rheine ${password.trim()} rheine`)
admin: permissions.admin,
article_create: permissions.admin
? false
: permissions.article_create,
article_manage: permissions.admin
? false
: permissions.article_manage,
sponsor_manage: permissions.admin
? false
: permissions.sponsor_manage,
user_manage: permissions.admin
? false
: permissions.user_manage,
}, },
{ {
headers: { headers: {
@ -339,20 +196,7 @@ function UserModal({
username, username,
password: password password: password
? sha256(`rheine ${password.trim()} rheine`) ? sha256(`rheine ${password.trim()} rheine`)
: undefined, : undefined
admin: permissions.admin,
article_create: permissions.admin
? false
: permissions.article_create,
article_manage: permissions.admin
? false
: permissions.article_manage,
sponsor_manage: permissions.admin
? false
: permissions.sponsor_manage,
user_manage: permissions.admin
? false
: permissions.user_manage,
}, },
{ {
headers: { headers: {

View File

@ -1,5 +1,5 @@
export function getBaseURL(): string { export function getBaseURL(): string {
if(process.env.NODE_ENV === "development") return "http://100.108.94.138:3001" if(process.env.NODE_ENV === "development") return "http://127.0.0.1:3001"
else return "" else return ""
} }

View File

@ -3,12 +3,16 @@ import "./index.css";
import App from "./App"; import App from "./App";
import { createTheme, ThemeProvider, CssBaseline } from "@mui/material"; import { createTheme, ThemeProvider, CssBaseline } from "@mui/material";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import moment from "moment";
// fonts // fonts
import '@fontsource-variable/overpass'; import '@fontsource-variable/overpass';
import '@fontsource-variable/lexend'; import '@fontsource-variable/lexend';
moment.locale("de");
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
mode: "light", mode: "light",
@ -41,7 +45,9 @@ root.render(
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<BrowserRouter> <BrowserRouter>
<LocalizationProvider dateAdapter={AdapterMoment}>
<App /> <App />
</LocalizationProvider>
</BrowserRouter> </BrowserRouter>
</ThemeProvider> </ThemeProvider>
); );

View File

@ -4,8 +4,8 @@ import Sidebar from "../components/Sidebar";
import Dashboard from "./admin/Dashboard"; import Dashboard from "./admin/Dashboard";
import Benutzer from "./admin/Benutzer"; import Benutzer from "./admin/Benutzer";
import Logout from "./admin/Logout"; import Logout from "./admin/Logout";
import Import from "./admin/Import";
import Kalender from "./admin/Kalender"; import Kalender from "./admin/Kalender";
import ManageData from "./admin/ManageData";
function AdminFrame() { function AdminFrame() {
return ( return (
@ -31,7 +31,7 @@ function AdminFrame() {
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard" element={<Dashboard />} />
<Route path="/benutzer" element={<Benutzer />} /> <Route path="/benutzer" element={<Benutzer />} />
<Route path="/logout" element={<Logout />} /> <Route path="/logout" element={<Logout />} />
<Route path="/import" element={<Import />} /> <Route path="/manage" element={<ManageData />} />
<Route path="/kalender" element={<Kalender />} /> <Route path="/kalender" element={<Kalender />} />
</Routes> </Routes>
</Box> </Box>

View File

@ -1,21 +1,52 @@
import { Box, Typography } from "@mui/material"; import { Box, Collapse, Typography } from "@mui/material";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import PhilSwift from "../components/PhilSwift"; import PhilSwift from "../components/PhilSwift";
import axios from "axios"; import axios from "axios";
import { getBaseURL } from "../functions"; 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() { function LandingPage() {
const [isHalloween, setIsHalloween] = useState(false); const [data, setData] = useState<EmployeeWithColor[]>([]);
const [currEvent, setCurrEvent] = useState<number>(0);
useEffect(() => { useEffect(() => {
const currentDate = new Date(); axios
const halloweenDate = new Date("2024-03-04"); .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( useEffect(() => {
currentDate.getMonth() === halloweenDate.getMonth() && const interval = setInterval(() => {
currentDate.getDate() === halloweenDate.getDate() 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 ( return (
@ -32,7 +63,7 @@ function LandingPage() {
alignItems: "center", alignItems: "center",
}} }}
> >
<PhilSwift /> {/* <PhilSwift /> */}
</Box> </Box>
<Box <Box
@ -48,20 +79,52 @@ function LandingPage() {
zIndex: 10, zIndex: 10,
}} }}
> >
{data.map((employee, index) => {
return (
<Collapse
in={currEvent === index}
key={index}
orientation="horizontal"
timeout={1000} // Add this line to make the collapse slower
easing={"ease-in-out"}
>
<Box sx={{
width: "100vw",
height: "100vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}>
<Typography <Typography
variant="h1"
sx={{ sx={{
fontSize: "2rem", width: "100vw",
color: "red",
textAlign: "center", textAlign: "center",
fontWeight: "bold", fontFamily: "Lexend Variable",
textShadow: "2px 2px 4px #000000", color: employee.color,
zIndex: 10, fontSize: "15rem",
}} }}
> >
{isHalloween {employee.Vorname} {employee.Nachname}
? "Happy Halloween"
: "Keine Geburtstage oder Jubiläen heute."}
</Typography> </Typography>
<Typography
variant="h2"
sx={{
width: "100vw",
textAlign: "center",
fontFamily: "Lexend Variable",
color: employee.color,
fontSize: "5rem",
}}
>
{employee.EventType === "Geburtstag" ? "hat Geburtstag" : "hat Jubiläum"}
</Typography>
</Box>
</Collapse>
);
})}
</Box> </Box>
</> </>
); );

View File

@ -112,7 +112,6 @@ function Benutzer() {
<TableCell align="left" width="700px"> <TableCell align="left" width="700px">
Name Name
</TableCell> </TableCell>
<TableCell align="right">Berechtigungen</TableCell>
<TableCell align="right"></TableCell> <TableCell align="right"></TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -120,18 +119,6 @@ function Benutzer() {
{users.map((user) => ( {users.map((user) => (
<TableRow> <TableRow>
<TableCell align="left">{user.username}</TableCell> <TableCell align="left">{user.username}</TableCell>
<TableCell align="right">
{[
{ 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(", ")}
</TableCell>
<TableCell align="right"> <TableCell align="right">
<Box <Box
sx={{ sx={{

View File

@ -1,115 +1,18 @@
import { Visibility, Newspaper, Person, Savings } from "@mui/icons-material"; import { Box, Typography } from '@mui/material'
import { Box, Grid, SvgIconTypeMap, Typography } from "@mui/material"; import React from 'react'
import { OverridableComponent } from "@mui/material/OverridableComponent";
import axios from "axios";
import { useEffect, useState } from "react";
import { getBaseURL } from "../../functions";
import { DataGrid, GridColDef, GridValueGetterParams } from "@mui/x-data-grid";
import moment from "moment";
const columns: GridColDef[] = [
{
field: "Vorname",
headerName: "Vorname",
width: 150,
},
{
field: "Nachname",
headerName: "Nachname",
width: 150,
},
{
field: "Anstelldatum",
headerName: "Anstelldatum",
type: "date",
width: 160,
valueFormatter: params => moment(params.value).format("DD.MM.YYYY")
},
{
field: "Geburtstag",
headerName: "Geburtstag",
type: "date",
width: 160,
valueFormatter: params => moment(params.value).format("DD.MM.YYYY")
},
];
function Dashboard() { function Dashboard() {
const [workers, setWorkers] = useState<Types.Worker[]>([]);
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 ( return (
<Box <Box sx={{
sx={{
width: "100%", width: "100%",
height: "auto", height: "100vh",
display: "flex", display: "flex",
flexDirection: "column", justifyContent: "center",
justifyContent: "flex-start", alignItems: "center",
alignItems: "flex-start", }}>
<Typography variant="h1">Noch nicht Implementiert!</Typography>
pt: "30px",
px: "30px",
}}
>
<Typography variant="h4" sx={{ fontWeight: "bold" }}>
Nächste Geburtstage
</Typography>
<DataGrid
rows={rows}
columns={columns}
sx={{
fontSize: "1rem",
marginTop: "1rem",
borderRadius: "20px",
width: "100%",
height: "auto",
}}
initialState={{
sorting: {
sortModel: [
{
field: 'commodity',
sort: 'asc',
},
],
},
pagination: {
paginationModel: {
pageSize: 15,
},
},
}}
checkboxSelection
/>
</Box> </Box>
); )
} }
export default Dashboard; export default Dashboard

View File

@ -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<Types.Employees[]>([]);
const [checked, setChecked] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [createPopup, setCreatePopup] = useState<boolean>(false);
const [editPopup, setEditPopup] = useState<Types.Employees | false>(false);
const [deletePopup, setDeletePopup] = useState<Types.Employees | false>(
false
);
const [uploadPopup, setUploadPopup] = useState<boolean>(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 (
<Box
sx={{
width: "100%",
height: "auto",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "flex-start",
pt: "30px",
px: "30px",
}}
>
<Backdrop open={loading} sx={{ zIndex: 1000 }}>
<CircularProgress color="secondary" />
</Backdrop>
{CreatePopUp(createPopup, setCreatePopup)}
{EditPopUp(editPopup, setEditPopup)}
{DeletePopUp(deletePopup, setDeletePopup)}
{UploadPopUp(uploadPopup, setUploadPopup)}
<Typography variant="h4" sx={{ fontWeight: "bold" }}>
Daten Verwalten
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
padding: "10px",
backgroundColor: "#00000022",
borderRadius: "5px",
ml: "auto",
}}
>
<Tooltip arrow placement="top" title="Mitarbeiter Hinzufügen">
<Button
onClick={() => {
setCreatePopup(true);
}}
>
<Add />
</Button>
</Tooltip>
<Tooltip arrow placement="top" title="Daten Aktualisieren">
<Button onClick={() => getEmployees()}>
<Refresh />
</Button>
</Tooltip>
<Tooltip arrow placement="top" title="CSV Hochladen">
<Button onClick={() => setUploadPopup(true)}>
<UploadFile />
</Button>
</Tooltip>
<Collapse orientation="horizontal" in={checked.length > 0}>
<Tooltip arrow placement="top" title="Ausgewählte Löschen">
<Button
onClick={() => {
setLoading(true);
axios
.delete(
`${getBaseURL()}/api/employees/${checked.join(",")}`,
{
headers: {
Authorization: `Bearer ${localStorage.getItem(
"token"
)}`,
},
}
)
.then(() => {
getEmployees();
})
.catch((err) => {
console.log(err);
});
}}
>
<Delete />
</Button>
</Tooltip>
</Collapse>
</Box>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={checked.length === employees.length}
onChange={(_, v) => {
if (v) setChecked(employees.map((employee) => employee.ID));
else setChecked([]);
}}
/>
</TableCell>
<TableCell>Nachname</TableCell>
<TableCell>Vorname</TableCell>
<TableCell>Geburtstag</TableCell>
<TableCell>Anstelldatum</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{employees.map((employee, index) => (
<TableRow key={index}>
<TableCell padding="checkbox">
<Checkbox
checked={checked.includes(employee.ID)}
onChange={(_, v) => {
if (v) setChecked([...checked, employee.ID]);
else setChecked(checked.filter((id) => id !== employee.ID));
}}
/>
</TableCell>
<TableCell>{employee.Nachname}</TableCell>
<TableCell>{employee.Vorname}</TableCell>
<TableCell>
{employee.Geburtstag
? moment(employee.Geburtstag).format("DD.MM.YYYY")
: "-"}
</TableCell>
<TableCell>
{employee.Anstelldatum
? moment(employee.Anstelldatum).format("DD.MM.YYYY")
: "-"}
</TableCell>
<TableCell align="right">
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
gap: "10px",
}}
>
<Button
variant="outlined"
onClick={() => {
setEditPopup(employee);
}}
>
<Edit />
</Button>
<Button
variant="outlined"
color="error"
onClick={() => {
setDeletePopup(employee);
}}
>
<Delete />
</Button>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
);
}
export default ManageData;
function CreatePopUp(
createPopup: boolean,
setCreatePopup: (value: boolean) => void
) {
const [vorname, setVorname] = useState<string>("");
const [nachname, setNachname] = useState<string>("");
const [showGeburtstag, setShowGeburtstag] = useState<boolean>(false);
const [geburtstag, setGeburtstag] = useState<moment.Moment | null>(null);
const [showAnstelldatum, setShowAnstelldatum] = useState<boolean>(false);
const [anstelldatum, setAnstelldatum] = useState<moment.Moment | null>(null);
const [loading, setLoading] = useState<boolean>(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 (
<Backdrop open={createPopup} sx={{ zIndex: 1000 }}>
<Box
sx={{
width: "500px",
height: "auto",
backgroundColor: "white",
borderRadius: "5px",
boxShadow: "0 0 10px #00000055",
padding: "20px",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
gap: "20px",
}}
>
<Typography sx={{ fontWeight: "bold", fontSize: "30px" }}>
Neuen Mitarbeiter Hinzufügen
</Typography>
<TextField
label="Vorname"
variant="outlined"
fullWidth
value={vorname}
maxRows={1}
onChange={(e) => {
if (e.target.value.length > 24) return;
setVorname(e.target.value);
}}
/>
<TextField
label="Nachname"
variant="outlined"
fullWidth
value={nachname}
maxRows={1}
onChange={(e) => {
if (e.target.value.length > 24) return;
setNachname(e.target.value);
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: "5px",
width: "100%",
}}
>
<Checkbox
checked={showGeburtstag}
onChange={(_, v) => setShowGeburtstag(v)}
/>
<DatePicker
label="Geburtstag"
sx={{ width: "100%" }}
format="DD.MM.YYYY"
value={geburtstag}
onChange={(v) => setGeburtstag(v)}
disabled={!showGeburtstag}
maxDate={moment() as any}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: "5px",
width: "100%",
}}
>
<Checkbox
checked={showAnstelldatum}
onChange={(_, v) => setShowAnstelldatum(v)}
/>
<DatePicker
label="Anstelldatum"
sx={{ width: "100%" }}
format="DD.MM.YYYY"
value={anstelldatum}
onChange={(v) => setAnstelldatum(v)}
disabled={!showAnstelldatum}
maxDate={moment() as any}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
padding: "10px",
gap: "10px",
}}
>
<Button
variant="contained"
disabled={!checkValid() || loading}
onClick={() => {
if (!checkValid()) return;
setLoading(true);
axios
.put(
`${getBaseURL()}/api/employees`,
[
{
Vorname: vorname,
Nachname: nachname,
Geburtstag: showGeburtstag
? moment(geburtstag).add(1, "day").toDate().getTime()
: null,
Anstelldatum: showAnstelldatum
? moment(anstelldatum).add(1, "day").toDate().getTime()
: null,
},
],
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
)
.then(() => {
setCreatePopup(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
}}
>
{loading && <CircularProgress size={20} />} Hinzufügen
</Button>
<Button
variant="outlined"
disabled={loading}
onClick={() => {
setCreatePopup(false);
}}
>
Abbrechen
</Button>
</Box>
</Box>
</Backdrop>
);
}
function EditPopUp(
editPopup: Types.Employees | false,
setEditPopup: (value: Types.Employees | false) => void
) {
const [vorname, setVorname] = useState<string>(
editPopup ? editPopup.Vorname : ""
);
const [nachname, setNachname] = useState<string>(
editPopup ? editPopup.Nachname : ""
);
const [showGeburtstag, setShowGeburtstag] = useState<boolean>(
editPopup ? !!editPopup.Geburtstag : false
);
const [geburtstag, setGeburtstag] = useState<moment.Moment | null>(
editPopup
? editPopup.Geburtstag
? moment(editPopup.Geburtstag)
: null
: null
);
const [showAnstelldatum, setShowAnstelldatum] = useState<boolean>(
editPopup ? !!editPopup.Anstelldatum : false
);
const [anstelldatum, setAnstelldatum] = useState<moment.Moment | null>(
editPopup
? editPopup.Anstelldatum
? moment(editPopup.Anstelldatum)
: null
: null
);
const [loading, setLoading] = useState<boolean>(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 (
<Backdrop open={editPopup !== false} sx={{ zIndex: 1000 }}>
<Box
sx={{
width: "500px",
height: "auto",
backgroundColor: "white",
borderRadius: "5px",
boxShadow: "0 0 10px #00000055",
padding: "20px",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
gap: "20px",
}}
>
<Typography sx={{ fontWeight: "bold", fontSize: "30px" }}>
{editPopup && editPopup.Vorname[0]}
{". "} {editPopup && editPopup.Nachname} Bearbeiten
</Typography>
<TextField
label="Vorname"
variant="outlined"
fullWidth
value={vorname}
maxRows={1}
onChange={(e) => {
if (e.target.value.length > 24) return;
setVorname(e.target.value);
}}
/>
<TextField
label="Nachname"
variant="outlined"
fullWidth
value={nachname}
maxRows={1}
onChange={(e) => {
if (e.target.value.length > 24) return;
setNachname(e.target.value);
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: "5px",
width: "100%",
}}
>
<Checkbox
checked={showGeburtstag}
onChange={(_, v) => setShowGeburtstag(v)}
/>
<DatePicker
label="Geburtstag"
sx={{ width: "100%" }}
format="DD.MM.YYYY"
value={geburtstag}
onChange={(v) => setGeburtstag(v)}
disabled={!showGeburtstag}
maxDate={moment() as any}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: "5px",
width: "100%",
}}
>
<Checkbox
checked={showAnstelldatum}
onChange={(_, v) => setShowAnstelldatum(v)}
/>
<DatePicker
label="Anstelldatum"
sx={{ width: "100%" }}
format="DD.MM.YYYY"
value={anstelldatum}
onChange={(v) => setAnstelldatum(v)}
disabled={!showAnstelldatum}
maxDate={moment() as any}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
padding: "10px",
gap: "10px",
}}
>
<Button
variant="contained"
disabled={!checkValid() || loading}
onClick={() => {
if (!checkValid()) return;
setLoading(true);
if (editPopup && !editPopup.ID) return;
axios
.patch(
`${getBaseURL()}/api/employees`,
[
{
ID: editPopup && editPopup.ID,
Vorname: vorname,
Nachname: nachname,
Geburtstag: showGeburtstag
? editPopup && editPopup.Geburtstag
? moment(geburtstag).toDate().getTime()
: moment(geburtstag).add(1, "day").toDate().getTime()
: null,
Anstelldatum: showAnstelldatum
? editPopup && editPopup.Anstelldatum
? moment(anstelldatum).toDate().getTime()
: moment(anstelldatum)
.add(1, "day")
.toDate()
.getTime()
: null,
},
],
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
)
.then(() => {
setEditPopup(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
}}
>
{loading && <CircularProgress size={20} />} Speichern
</Button>
<Button
variant="outlined"
disabled={loading}
onClick={() => {
setEditPopup(false);
}}
>
Abbrechen
</Button>
</Box>
</Box>
</Backdrop>
);
}
function DeletePopUp(
deletePopup: Types.Employees | false,
setDeletePopup: (value: Types.Employees | false) => void
) {
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
if (!deletePopup) setLoading(false);
}, [deletePopup]);
if (deletePopup === false) return <></>;
return (
<Backdrop open={Boolean(deletePopup)} sx={{ zIndex: 1000 }}>
<Box
sx={{
width: "500px",
height: "auto",
backgroundColor: "white",
borderRadius: "5px",
boxShadow: "0 0 10px #00000055",
padding: "20px",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
gap: "20px",
}}
>
<Typography sx={{ fontWeight: "bold", fontSize: "30px" }}>
Mitarbeiter Löschen
</Typography>
<Typography>
Wollen Sie wirklich den Mitarbeiter{" "}
<strong>
{deletePopup?.Vorname} {deletePopup?.Nachname}{" "}
</strong>
löschen?
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
padding: "10px",
gap: "10px",
}}
>
<Button
variant="contained"
color="error"
disabled={loading}
onClick={() => {
setLoading(true);
if (deletePopup && !deletePopup.ID) return;
axios
.delete(`${getBaseURL()}/api/employees/${deletePopup?.ID}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then(() => {
setDeletePopup(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
}}
>
{loading && <CircularProgress size={20} />} Löschen
</Button>
<Button
variant="outlined"
disabled={loading}
onClick={() => {
setDeletePopup(false);
}}
>
Abbrechen
</Button>
</Box>
</Box>
</Backdrop>
);
}
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<boolean>(false);
const [data, setData] = useState<EmployeePartial[] | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!uploadPopup) setLoading(false);
}, [uploadPopup]);
return (
<Backdrop open={uploadPopup} sx={{ zIndex: 1000 }}>
<Box
sx={{
width: "500px",
height: "auto",
backgroundColor: "white",
borderRadius: "5px",
boxShadow: "0 0 10px #00000055",
padding: "20px",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
gap: "20px",
}}
>
<Typography sx={{ fontWeight: "bold", fontSize: "30px" }}>
CSV Hochladen
</Typography>
<Collapse in={error !== null}>
<Alert severity="error">{error}</Alert>
</Collapse>
<input
type="file"
onChange={(e) => {
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<string[]> = 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);
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
padding: "10px",
gap: "10px",
}}
>
<Button
variant="contained"
disabled={loading || !data}
onClick={() => {
setLoading(true);
axios.put(`${getBaseURL()}/api/employees`, data, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
}}
>
{loading && <CircularProgress size={20} />} Hochladen
</Button>
<Button
variant="outlined"
disabled={loading}
onClick={() => {
setUploadPopup(false);
}}
>
Abbrechen
</Button>
</Box>
</Box>
</Backdrop>
);
}

View File

@ -1,9 +1,10 @@
declare namespace Types { declare namespace Types {
interface Worker { interface Employees {
ID: string;
Vorname: string; Vorname: string;
Nachname: string; Nachname: string;
Geburtstag: Date; Geburtstag?: Date;
Anstelldatum: Date; Anstelldatum?: Date;
} }
interface User extends UserPermissions { interface User extends UserPermissions {
ID: string; ID: string;
@ -17,12 +18,4 @@ declare namespace Types {
sponsor_manage: boolean; sponsor_manage: boolean;
user_manage: boolean; user_manage: boolean;
} }
interface Sponsor {
ID: string;
name: string;
url: string;
description: string;
addedAt: Date;
}
} }