enhance dashboard and user interface: update heading sizes, translate user label to German, and implement loan management features including fetching and displaying loans with error handling

This commit is contained in:
2025-09-02 18:51:41 +02:00
parent 769d1117eb
commit b217769961
8 changed files with 262 additions and 32 deletions

View File

@@ -36,7 +36,7 @@ const Dashboard: React.FC<DashboardProps> = ({ onLogout }) => {
borderColor="gray.200" borderColor="gray.200"
bg="gray.900" bg="gray.900"
> >
<Heading size="md">Dashboard</Heading> <Heading size="xl">Dashboard</Heading>
<Flex align="center" gap={6}> <Flex align="center" gap={6}>
<Text fontSize="sm" color="white"> <Text fontSize="sm" color="white">
Willkommen {userName}, im Admin-Dashboard! Willkommen {userName}, im Admin-Dashboard!

View File

@@ -39,7 +39,7 @@ const Sidebar: React.FC<SidebarProps> = ({
_hover={{ bg: "gray.700", textDecoration: "none" }} _hover={{ bg: "gray.700", textDecoration: "none" }}
onClick={viewUser} onClick={viewUser}
> >
User Benutzer
</Link> </Link>
<Link <Link
px={3} px={3}

View File

@@ -1,7 +1,173 @@
import React from "react"; import React from "react";
import {
Table,
Code,
VStack,
Spinner,
Text,
Heading,
Button,
} from "@chakra-ui/react";
import { useState, useEffect } from "react";
import Cookies from "js-cookie";
import MyAlert from "./myChakra/MyAlert";
import { formatDateTime } from "@/utils/userFuncs";
import { Trash2 } from "lucide-react";
import { deleteLoan } from "@/utils/userActions";
const LoanTable: React.FC = () => { const LoanTable: React.FC = () => {
return <>Loan Table</>; const [items, setItems] = useState<Loan[]>([]);
const [errorStatus, setErrorStatus] = useState<"error" | "success">("error");
const [errorMessage, setErrorMessage] = useState("");
const [errorDsc, setErrorDsc] = useState("");
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const setError = (
status: "error" | "success",
message: string,
description: string
) => {
setIsError(false);
setErrorStatus(status);
setErrorMessage(message);
setErrorDsc(description);
setIsError(true);
};
type Loan = {
id: number;
username: string;
loan_code: string;
start_date: string;
end_date: string;
take_date: string;
returned_date: string;
created_at: string;
loaned_items_name: string[];
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch("http://localhost:8002/api/allLoans", {
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
});
const data = await response.json();
return data;
} catch (error) {
setError("error", "Failed to fetch loans", "There is an error");
} finally {
setIsLoading(false);
}
};
fetchData().then((data) => {
if (Array.isArray(data)) {
setItems(data);
}
});
}, []);
return (
<>
<Heading marginBottom={4} size="md">
Ausleihen
</Heading>
{isError && (
<MyAlert
status={errorStatus}
description={errorDsc}
title={errorMessage}
/>
)}
{isLoading && (
<VStack colorPalette="teal">
<Spinner color="colorPalette.600" />
<Text color="colorPalette.600">Loading...</Text>
</VStack>
)}
{!isLoading && (
<Table.Root size="sm" striped>
<Table.Header>
<Table.Row>
<Table.ColumnHeader>
<strong>#</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Besitzer</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Ausleih code</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Startdatum</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Enddatum</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Ausleihdatum</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Rückgabedatum</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Erstellt am</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Ausgeliehene Artikel</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Aktionen</strong>
</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{items.map((item) => (
<Table.Row key={item.id}>
<Table.Cell>{item.id}</Table.Cell>
<Table.Cell>{item.username}</Table.Cell>
<Table.Cell>
<Code>{item.loan_code}</Code>
</Table.Cell>
<Table.Cell>{formatDateTime(item.start_date)}</Table.Cell>
<Table.Cell>{formatDateTime(item.end_date)}</Table.Cell>
<Table.Cell>{formatDateTime(item.take_date)}</Table.Cell>
<Table.Cell>{formatDateTime(item.returned_date)}</Table.Cell>
<Table.Cell>{formatDateTime(item.created_at)}</Table.Cell>
<Table.Cell>{item.loaned_items_name.join(", ")}</Table.Cell>
<Table.Cell>
<Button
onClick={() =>
deleteLoan(item.id).then((response) => {
if (response.success) {
setItems(items.filter((i) => i.id !== item.id));
setError(
"success",
"Loan deleted",
"The loan has been successfully deleted."
);
}
})
}
colorPalette="red"
size="sm"
ml={2}
>
<Trash2 />
</Button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
)}
</>
);
}; };
export default LoanTable; export default LoanTable;

View File

@@ -9,6 +9,7 @@ import {
Input, Input,
HStack, HStack,
IconButton, IconButton,
Heading,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { Tooltip } from "@/components/ui/tooltip"; import { Tooltip } from "@/components/ui/tooltip";
import { fetchUserData } from "@/utils/fetcher"; import { fetchUserData } from "@/utils/fetcher";
@@ -16,6 +17,7 @@ import { Save, Trash2, RefreshCcwDot, CirclePlus } from "lucide-react";
import { handleDelete, handleEdit } from "@/utils/userActions"; import { handleDelete, handleEdit } from "@/utils/userActions";
import MyAlert from "./myChakra/MyAlert"; import MyAlert from "./myChakra/MyAlert";
import AddForm from "./AddForm"; import AddForm from "./AddForm";
import { formatDateTime } from "@/utils/userFuncs";
type User = { type User = {
id: number; id: number;
@@ -134,6 +136,9 @@ const UserTable: React.FC = () => {
</HStack> </HStack>
{/* End action toolbar */} {/* End action toolbar */}
<Heading marginBottom={4} size="md">
Benutzer
</Heading>
{isError && ( {isError && (
<MyAlert <MyAlert
status={errorStatus} status={errorStatus}
@@ -160,12 +165,24 @@ const UserTable: React.FC = () => {
<Table.Root size="sm" striped> <Table.Root size="sm" striped>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.ColumnHeader>id</Table.ColumnHeader> <Table.ColumnHeader>
<Table.ColumnHeader>Username</Table.ColumnHeader> <strong>#</strong>
<Table.ColumnHeader>Password</Table.ColumnHeader> </Table.ColumnHeader>
<Table.ColumnHeader>Role</Table.ColumnHeader> <Table.ColumnHeader>
<Table.ColumnHeader>Entry Created At</Table.ColumnHeader> <strong>Benutzername</strong>
<Table.ColumnHeader>Actions</Table.ColumnHeader> </Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Passwort</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Rolle</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Eintrag erstellt am</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Aktionen</strong>
</Table.ColumnHeader>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
@@ -197,7 +214,7 @@ const UserTable: React.FC = () => {
value={user.role} value={user.role}
/> />
</Table.Cell> </Table.Cell>
<Table.Cell>{user.entry_created_at}</Table.Cell> <Table.Cell>{formatDateTime(user.entry_created_at)}</Table.Cell>
<Table.Cell> <Table.Cell>
<Button <Button
onClick={() => onClick={() =>

View File

@@ -50,28 +50,46 @@ export const handleEdit = async (
}; };
export const createUser = async ( export const createUser = async (
username: string, username: string,
role: number, role: number,
password: string password: string
) => { ) => {
try { try {
const response = await fetch( const response = await fetch(`http://localhost:8002/api/createUser`, {
`http://localhost:8002/api/createUser`, method: "POST",
{ headers: {
method: "POST", "Content-Type": "application/json",
headers: { Authorization: `Bearer ${Cookies.get("token")}`,
"Content-Type": "application/json", },
Authorization: `Bearer ${Cookies.get("token")}`, body: JSON.stringify({ username, role, password }),
}, });
body: JSON.stringify({ username, role, password }), if (!response.ok) {
} throw new Error("Failed to create user");
);
if (!response.ok) {
throw new Error("Failed to create user");
}
return { success: true };
} catch (error) {
console.error("Error creating user:", error);
return { success: false };
} }
return { success: true };
} catch (error) {
console.error("Error creating user:", error);
return { success: false };
}
};
export const deleteLoan = async (loanId: number) => {
try {
const response = await fetch(
`http://localhost:8002/api/deleteLoan/${loanId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
}
);
if (!response.ok) {
throw new Error("Failed to delete loan");
}
return { success: true };
} catch (error) {
console.error("Error deleting loan:", error);
return { success: false };
}
}; };

View File

@@ -0,0 +1,14 @@
export const formatDateTime = (value: string | null | undefined) => {
if (!value) return "N/A";
const inpDate = new Date(value);
if (isNaN(inpDate.getTime())) return "N/A";
return (
inpDate.toLocaleString(undefined, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
}) + " Uhr"
);
};

View File

@@ -14,6 +14,7 @@ import {
deleteUserID, deleteUserID,
handleEdit, handleEdit,
createUser, createUser,
getAllLoans,
} from "../services/database.js"; } from "../services/database.js";
import { authenticate, generateToken } from "../services/tokenService.js"; import { authenticate, generateToken } from "../services/tokenService.js";
const router = express.Router(); const router = express.Router();
@@ -238,4 +239,12 @@ router.post("/createUser", authenticate, async (req, res) => {
return res.status(500).json({ message: "Failed to create user" }); return res.status(500).json({ message: "Failed to create user" });
}); });
router.get("/allLoans", authenticate, async (req, res) => {
const result = await getAllLoans();
if (result.success) {
return res.status(200).json(result.data);
}
return res.status(500).json({ message: "Failed to fetch loans" });
});
export default router; export default router;

View File

@@ -358,3 +358,9 @@ export const createUser = async (username, role, password) => {
if (result.affectedRows > 0) return { success: true }; if (result.affectedRows > 0) return { success: true };
return { success: false }; return { success: false };
}; };
export const getAllLoans = async () => {
const [result] = await pool.query("SELECT * FROM loans");
if (result.length > 0) return { success: true, data: result };
return { success: false };
};