diff --git a/components/user/SelectionActions.tsx b/components/user/SelectionActions.tsx
new file mode 100644
index 0000000..8cb85b4
--- /dev/null
+++ b/components/user/SelectionActions.tsx
@@ -0,0 +1,29 @@
+import PlusOneIcon from "@mui/icons-material/PlusOne";
+import { IconButton, Tooltip } from "@mui/material";
+
+type SelectionActionsPropsType = {
+ checked: any;
+ increaseGrade: any;
+};
+
+export default function SelectionActions({
+ checked,
+ increaseGrade,
+}: SelectionActionsPropsType) {
+ const checkedItems = Object.values(checked).filter((item) => item != false);
+ if (checkedItems.length > 0) {
+ //console.log("Checked Items", checkedItems);
+ return (
+
+
+
+
+
+ );
+ } else return
;
+}
diff --git a/components/user/UserAdminList.tsx b/components/user/UserAdminList.tsx
index 848eff6..b7a4f6b 100644
--- a/components/user/UserAdminList.tsx
+++ b/components/user/UserAdminList.tsx
@@ -1,6 +1,6 @@
import Typography from "@mui/material/Typography";
-import { Avatar, Grid } from "@mui/material";
+import { Avatar, Checkbox, Grid, Paper } from "@mui/material";
import { UserType } from "@/entities/UserType";
import palette from "@/styles/palette";
@@ -12,22 +12,40 @@ import AccordionSummary from "@mui/material/AccordionSummary";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { RentalsUserType } from "@/entities/RentalsUserType";
+import { useEffect } from "react";
import UserDetailsCard from "./UserDetailsCard";
type UserAdminListPropsType = {
users: Array;
rentals: Array;
searchString: string;
+ checked: any;
+ setChecked: any;
};
export default function UserAdminList({
users,
rentals,
searchString,
+ checked,
+ setChecked,
}: UserAdminListPropsType) {
//attach amount of rented books to the user
+
const rentalAmount: { [key: number]: number } = {};
+ //initialise user checked array
+
+ useEffect(() => {
+ const checkSet: { [key: string]: boolean } = users.reduce((acc, obj) => {
+ const userID = obj.id!.toString();
+ acc[userID] = false; // Initialize each key-value pair using the "id" field
+ return acc;
+ }, {} as { [key: string]: boolean });
+ setChecked(checkSet);
+ //console.log("Initialised user checkboxes", checkSet);
+ }, [users]);
+
rentals.map((r: any) => {
if (r.userid in rentalAmount) {
rentalAmount[r.userid] = rentalAmount[r.userid] + 1;
@@ -38,53 +56,93 @@ export default function UserAdminList({
{users.map((u: UserType) => {
const lowerCaseSearch = searchString.toLowerCase();
+ //console.log("Checked", checked);
+ const userID = u.id!.toString();
+ const checkBoxValue =
+ userID in checked ? (checked[userID] as boolean) : false;
if (
u.lastName.toLowerCase().includes(lowerCaseSearch) ||
u.firstName.toLowerCase().includes(lowerCaseSearch) ||
u.id!.toString().includes(lowerCaseSearch)
)
return (
-
- }
- aria-controls="panel1a-content"
- id="panel1a-header"
+
+
-
-
- {rentalAmount[u.id!] != undefined ? (
-
- {rentalAmount[u.id!]}
-
- ) : (
-
- 0
-
- )}
-
-
- {u.lastName + ", " + u.firstName}
-
- {"Klasse " + u.schoolGrade + " - " + u.schoolTeacherName}
-
-
+
+ {
+ setChecked({ ...checked, [userID]: !checkBoxValue });
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+
+
+ }
+ aria-controls="panel1a-content"
+ id="panel1a-header"
+ >
+ {" "}
+
+
+ {rentalAmount[u.id!] != undefined ? (
+
+ {rentalAmount[u.id!]}
+
+ ) : (
+
+ 0
+
+ )}
+
+
+
+ {u.lastName + ", " + u.firstName}
+
+
+ {"Klasse " +
+ u.schoolGrade +
+ " - " +
+ u.schoolTeacherName}
+
+
+
+
+
+ parseInt(r.userid) == u.id
+ )}
+ />
+
+
-
-
- parseInt(r.userid) == u.id
- )}
- />
-
-
+
+
);
})}
diff --git a/entities/user.ts b/entities/user.ts
index fe2f889..99010af 100644
--- a/entities/user.ts
+++ b/entities/user.ts
@@ -110,6 +110,38 @@ export async function updateUser(
}
}
+export async function increaseUserGrade(
+ client: PrismaClient,
+ newGrades: Array<{ id: number; grade: string }>
+) {
+ try {
+ //create a transaction otherwise for single API calls, there's a connection pool issue
+ const transaction = [] as Array;
+ newGrades.map((i: { id: number; grade: string }) => {
+ transaction.push(
+ client.user.update({
+ where: {
+ id: i.id,
+ },
+ data: { schoolGrade: i.grade },
+ })
+ );
+ });
+
+ const result = await client.$transaction(transaction);
+ console.log("Batch update database operation succeeded: ", result);
+ return result;
+ } catch (e) {
+ if (
+ e instanceof Prisma.PrismaClientKnownRequestError ||
+ e instanceof Prisma.PrismaClientValidationError
+ ) {
+ console.log("ERROR in updating batch grades for user : ", e);
+ }
+ throw e;
+ }
+}
+
export async function disableUser(client: PrismaClient, id: number) {
await addAudit(client, "Disable user", id.toString(), 0, id);
return await client.user.update({
diff --git a/pages/api/batch/grade.ts b/pages/api/batch/grade.ts
new file mode 100644
index 0000000..e026ae4
--- /dev/null
+++ b/pages/api/batch/grade.ts
@@ -0,0 +1,31 @@
+import { increaseUserGrade } from "@/entities/user";
+import { PrismaClient } from "@prisma/client";
+import type { NextApiRequest, NextApiResponse } from "next";
+
+const prisma = new PrismaClient();
+
+export default async function handle(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ switch (req.method) {
+ case "POST":
+ try {
+ if (!req.body) return res.status(404).end("No data provided");
+ //gets a list of user IDs to update the grade
+ const userdata = req.body;
+
+ const updateResult = await increaseUserGrade(prisma, userdata);
+
+ res.status(200).json(updateResult);
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({ data: "ERROR DELETE: " + error });
+ }
+ break;
+
+ default:
+ res.status(405).end(`${req.method} Not Allowed`);
+ break;
+ }
+}
diff --git a/pages/user/index.tsx b/pages/user/index.tsx
index d457741..1887c74 100644
--- a/pages/user/index.tsx
+++ b/pages/user/index.tsx
@@ -16,11 +16,22 @@ import dayjs from "dayjs";
import { convertDateToDayString } from "@/utils/dateutils";
+import SelectionActions from "@/components/user/SelectionActions";
import UserDetailsCard from "@/components/user/UserDetailsCard";
import { BookType } from "@/entities/BookType";
import { RentalsUserType } from "@/entities/RentalsUserType";
import { UserType } from "@/entities/UserType";
-import { Divider, IconButton, InputBase, Paper, Tooltip } from "@mui/material";
+import { increaseNumberInString } from "@/utils/increaseNumberInString";
+import DoneAllIcon from "@mui/icons-material/DoneAll";
+import {
+ Alert,
+ Divider,
+ IconButton,
+ InputBase,
+ Paper,
+ Snackbar,
+ Tooltip,
+} from "@mui/material";
const prisma = new PrismaClient();
/*
@@ -40,12 +51,25 @@ export default function Users({ users, books, rentals }: UsersPropsType) {
const [userSearchInput, setUserSearchInput] = useState("");
const [displayDetail, setDisplayDetail] = useState(0);
const [userCreating, setUserCreating] = useState(false);
+ const [checked, setChecked] = useState({} as any);
+ const [batchEditSnackbar, setBatchEditSnackbar] = useState(false);
const router = useRouter();
const theme = useTheme();
useEffect(() => {}, []);
+ const handleBatchEditSnackbar = (
+ event?: React.SyntheticEvent | Event,
+ reason?: string
+ ) => {
+ if (reason === "clickaway") {
+ return;
+ }
+
+ setBatchEditSnackbar(false);
+ };
+
const handleInputChange = (e: ChangeEvent) => {
setUserSearchInput(e.target.value);
};
@@ -75,11 +99,53 @@ export default function Users({ users, books, rentals }: UsersPropsType) {
router.push("user/" + id);
};
+ const handleSelectAll = () => {
+ var resultCheck = true;
+
+ //if there is something selected, deselect all
+ Object.values(checked).some((value) => value === true)
+ ? (resultCheck = false)
+ : (resultCheck = true);
+ //console.log("Selecting or deselecting all users ", users);
+ const newChecked = users.reduce((acc: any, u: any) => {
+ if (u.id !== undefined) {
+ acc = { ...acc, [u.id]: resultCheck };
+ }
+ return acc;
+ }, {});
+ //console.log("New checked users", newChecked);
+ setChecked(newChecked);
+ };
+
const selectItem = (id: string) => {
console.log("selected user", users, rentals);
setDisplayDetail(parseInt(id));
};
+ const handleIncreaseGrade = () => {
+ //console.log("Increasing grade for users ", users, checked);
+ //the user IDs that are checked are marked as true
+ const updatedUserIDs = users.reduce((acc: any, u: UserType) => {
+ if (checked[u.id!])
+ acc.push({ id: u.id, grade: increaseNumberInString(u.schoolGrade) });
+ return acc;
+ }, []);
+
+ fetch("/api/batch/grade", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(updatedUserIDs),
+ })
+ .then((res) => res.json())
+ .then((data) => {
+ console.log("Users increased", data);
+ setBatchEditSnackbar(true);
+ router.push("user");
+ });
+ };
+
const booksForUser = (id: number) => {
const userRentals = rentals.filter((r: RentalsUserType) => r.userid == id);
//console.log("Filtered rentals", userRentals);
@@ -89,6 +155,19 @@ export default function Users({ users, books, rentals }: UsersPropsType) {
return (
+
+
+ Selektierte Benutzer angepasst, super!
+
+
+
+
+
+
+
+
+
+
+
{" "}
{displayDetail > 0 ? (
@@ -152,6 +248,8 @@ export default function Users({ users, books, rentals }: UsersPropsType) {
users={users}
rentals={rentals}
searchString={userSearchInput}
+ checked={checked}
+ setChecked={setChecked}
/>
diff --git a/utils/increaseNumberInString.ts b/utils/increaseNumberInString.ts
new file mode 100644
index 0000000..ebe2de0
--- /dev/null
+++ b/utils/increaseNumberInString.ts
@@ -0,0 +1,6 @@
+export function increaseNumberInString(text: any) {
+ return text.replace(/\d+/g, function (match: any) {
+ // Convert the matched substring to a number, add 1, and return it
+ return parseInt(match, 10) + 1;
+ });
+}