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"
bg="gray.900"
>
<Heading size="md">Dashboard</Heading>
<Heading size="xl">Dashboard</Heading>
<Flex align="center" gap={6}>
<Text fontSize="sm" color="white">
Willkommen {userName}, im Admin-Dashboard!

View File

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

View File

@@ -1,7 +1,173 @@
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 = () => {
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;

View File

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

View File

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