diff --git a/admin/src/App.tsx b/admin/src/App.tsx
index 2269c2e..f0933ae 100644
--- a/admin/src/App.tsx
+++ b/admin/src/App.tsx
@@ -4,9 +4,7 @@ import Layout from "./Layout/Layout";
function App() {
return (
<>
-
-
-
+
>
);
}
diff --git a/admin/src/Layout/Layout.tsx b/admin/src/Layout/Layout.tsx
index f096e02..d2d5c77 100644
--- a/admin/src/Layout/Layout.tsx
+++ b/admin/src/Layout/Layout.tsx
@@ -3,15 +3,20 @@ import { useEffect } from "react";
import Dashboard from "./Dashboard";
import Login from "./Login";
import Cookies from "js-cookie";
+import Landingpage from "@/components/API/Landingpage";
-type LayoutProps = {
- children: React.ReactNode;
-};
-
-const Layout: React.FC = ({ children }) => {
+const Layout: React.FC = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const [showAPI, setShowAPI] = useState(false);
useEffect(() => {
+ const path = window.location.pathname.replace(/\/+$/, ""); // remove trailing slash
+ if (path === "/api") {
+ setShowAPI(true);
+ console.log("signal");
+ return;
+ }
+
if (Cookies.get("token")) {
const verifyToken = async () => {
const response = await fetch("https://backend.insta.the1s.de/api/verifyToken", {
@@ -37,17 +42,22 @@ const Layout: React.FC = ({ children }) => {
setIsLoggedIn(false);
};
- return (
- <>
+ if (showAPI) {
+ return (
- {isLoggedIn ? (
- handleLogout()} />
- ) : (
- setIsLoggedIn(true)} />
- )}
+
- {children}
- >
+ );
+ }
+
+ return (
+
+ {isLoggedIn ? (
+ handleLogout()} />
+ ) : (
+ setIsLoggedIn(true)} />
+ )}
+
);
};
diff --git a/admin/src/components/API/Landingpage.tsx b/admin/src/components/API/Landingpage.tsx
new file mode 100644
index 0000000..51fd700
--- /dev/null
+++ b/admin/src/components/API/Landingpage.tsx
@@ -0,0 +1,181 @@
+import React, { useEffect, useState } from "react";
+import {
+ Spinner,
+ Text,
+ VStack,
+ Table,
+ Heading,
+ HStack,
+ IconButton,
+} from "@chakra-ui/react";
+import { Tooltip } from "@/components/ui/tooltip";
+import { RefreshCcwDot } from "lucide-react";
+import MyAlert from "../myChakra/MyAlert";
+import { formatDateTime } from "@/utils/userFuncs";
+
+type Loan = {
+ id: number;
+ username: string;
+ start_date: string;
+ end_date: string;
+ returned_date: string | null;
+ take_date: string | null;
+ loaned_items_name: string[] | string;
+};
+
+const Landingpage: React.FC = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [loans, setLoans] = useState([]);
+ const [isError, setIsError] = useState(false);
+ const [errorStatus, setErrorStatus] = useState<"error" | "success">("error");
+ const [errorMessage, setErrorMessage] = useState("");
+ const [errorDsc, setErrorDsc] = useState("");
+ const [reload, setReload] = useState(false);
+
+ const setError = (
+ status: "error" | "success",
+ message: string,
+ description: string
+ ) => {
+ setIsError(false);
+ setErrorStatus(status);
+ setErrorMessage(message);
+ setErrorDsc(description);
+ setIsError(true);
+ };
+
+ useEffect(() => {
+ const fetchLoans = async () => {
+ setIsLoading(true);
+ try {
+ const res = await fetch("http://localhost:8002/apiV2/allLoans");
+ const data = await res.json();
+ if (Array.isArray(data)) {
+ setLoans(data);
+ } else {
+ setError(
+ "error",
+ "Fehler beim Laden",
+ "Unerwartetes Datenformat erhalten."
+ );
+ }
+ } catch (e) {
+ setError(
+ "error",
+ "Fehler beim Laden",
+ "Die Ausleihen konnten nicht geladen werden."
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchLoans();
+ }, [reload]);
+
+ return (
+ <>
+
+ Matthias-Claudius-Schule Technik
+
+
+ {/* Action toolbar */}
+
+
+ setReload(!reload)}
+ >
+
+
+
+
+ {/* End action toolbar */}
+
+
+ Alle Ausleihen
+
+
+ {isError && (
+
+ )}
+
+ {isLoading && (
+
+
+ Loading...
+
+ )}
+
+ {!isLoading && (
+
+
+
+
+ #
+
+
+ Benutzername
+
+
+ Startdatum
+
+
+ Enddatum
+
+
+ Ausgeliehene Artikel
+
+
+ Rückgabedatum
+
+
+ Ausleihdatum
+
+
+
+
+ {loans.map((loan) => (
+
+ {loan.id}
+ {loan.username}
+ {formatDateTime(loan.start_date)}
+ {formatDateTime(loan.end_date)}
+
+ {Array.isArray(loan.loaned_items_name)
+ ? loan.loaned_items_name.join(", ")
+ : loan.loaned_items_name}
+
+ {formatDateTime(loan.returned_date)}
+ {formatDateTime(loan.take_date)}
+
+ ))}
+
+
+ )}
+
+ {!isLoading && loans.length === 0 && !isError && (
+
+ Keine Ausleihen vorhanden.
+
+ )}
+ >
+ );
+};
+
+export default Landingpage;
diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx
index 79b372f..da45f16 100644
--- a/admin/src/components/ItemTable.tsx
+++ b/admin/src/components/ItemTable.tsx
@@ -9,7 +9,6 @@ import {
IconButton,
Heading,
Icon,
- Tag,
Input,
} from "@chakra-ui/react";
import { Tooltip } from "@/components/ui/tooltip";
@@ -219,57 +218,34 @@ const ItemTable: React.FC = () => {
onClick={() =>
changeSafeState(item.id).then(() => setReload(!reload))
}
- size="sm"
+ size="xs"
+ rounded="full"
+ px={3}
+ py={1}
+ gap={2}
+ variant="ghost"
+ color={item.inSafe ? "green.600" : "red.600"}
+ borderWidth="1px"
+ borderColor={item.inSafe ? "green.300" : "red.300"}
+ _hover={{
+ bg: item.inSafe ? "green.50" : "red.50",
+ borderColor: item.inSafe ? "green.400" : "red.400",
+ transform: "translateY(-1px)",
+ shadow: "sm",
+ }}
+ _active={{ transform: "translateY(0)" }}
+ aria-label={
+ item.inSafe ? "Mark as not in safe" : "Mark as in safe"
+ }
>
- {item.inSafe ? (
-
-
-
- Yes
-
-
- ) : (
-
-
-
- No
-
-
- )}
+
+
+ {item.inSafe ? "Yes" : "No"}
+
{formatDateTime(item.entry_created_at)}
diff --git a/backend/routes/apiV2.js b/backend/routes/apiV2.js
index faecec2..1b81426 100644
--- a/backend/routes/apiV2.js
+++ b/backend/routes/apiV2.js
@@ -6,6 +6,7 @@ import {
setReturnDateV2,
setTakeDateV2,
getLoanByCodeV2,
+ getAllLoansV2,
} from "../services/database.js";
dotenv.config();
@@ -45,6 +46,7 @@ router.post("/controlInSafe/:key/:itemId/:state", async (req, res) => {
}
});
+// Route for API to get a loan by its code
router.get("/getLoanByCode/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
const loan_code = req.params.loan_code;
@@ -58,7 +60,7 @@ router.get("/getLoanByCode/:key/:loan_code", async (req, res) => {
}
});
-// Route for API to set the return date
+// Route for API to set the return date by the loan code
router.post("/setReturnDate/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
const loanCode = req.params.loan_code;
@@ -74,7 +76,7 @@ router.post("/setReturnDate/:key/:loan_code", async (req, res) => {
}
});
-// Route for API to set the take away date
+// Route for API to set the take away date by the loan code
router.post("/setTakeDate/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
const loanCode = req.params.loan_code;
@@ -90,4 +92,13 @@ router.post("/setTakeDate/:key/:loan_code", async (req, res) => {
}
});
+// Route for API to get ALL loans from the database without sensitive info
+router.get("/allLoans", async (req, res) => {
+ const result = await getAllLoansV2();
+ if (result.success) {
+ return res.status(200).json(result.data);
+ }
+ return res.status(500).json({ message: "Failed to fetch loans" });
+});
+
export default router;
diff --git a/backend/services/database.js b/backend/services/database.js
index 7bf442d..a102c2d 100644
--- a/backend/services/database.js
+++ b/backend/services/database.js
@@ -448,3 +448,13 @@ export const updateItemByID = async (itemId, item_name, can_borrow_role) => {
if (result.affectedRows > 0) return { success: true };
return { success: false };
};
+
+export const getAllLoansV2 = async () => {
+ const [rows] = await pool.query(
+ "SELECT id, username, start_date, end_date, loaned_items_name, returned_date, take_date FROM loans"
+ );
+ if (rows.length > 0) {
+ return { success: true, data: rows };
+ }
+ return { success: false };
+};