From 4fc60a08d9f8ce5bcfd085123029de1f96e7df2c Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Tue, 16 Sep 2025 13:48:56 +0200 Subject: [PATCH 1/6] refactor: update button for safe state with improved styling and text display --- admin/src/components/ItemTable.tsx | 79 +++++++++++------------------- 1 file changed, 28 insertions(+), 51 deletions(-) diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx index 604113b..c33c76a 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,35 @@ 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)" }} + leftIcon={ + + } + 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)} From c3572a3d70fc5c2c3aa002306292586e65f7e94b Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Tue, 16 Sep 2025 13:49:44 +0200 Subject: [PATCH 2/6] fix: adjust icon placement and styling for safe state indicator --- admin/src/components/ItemTable.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx index c33c76a..ec233a8 100644 --- a/admin/src/components/ItemTable.tsx +++ b/admin/src/components/ItemTable.tsx @@ -234,16 +234,15 @@ const ItemTable: React.FC = () => { shadow: "sm", }} _active={{ transform: "translateY(0)" }} - leftIcon={ - - } aria-label={ item.inSafe ? "Mark as not in safe" : "Mark as in safe" } > + {item.inSafe ? "Yes" : "No"} From 679ef7dcbd5a80da4a38a1e4bb7a7316a2f39368 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Fri, 19 Sep 2025 12:24:17 +0200 Subject: [PATCH 3/6] feat: implement Landingpage component and update Layout to conditionally render it --- admin/src/App.tsx | 4 +-- admin/src/Layout/Layout.tsx | 38 +++++++++++++++--------- admin/src/components/API/Landingpage.tsx | 11 +++++++ 3 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 admin/src/components/API/Landingpage.tsx 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 b7df89a..f5d3f74 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("http://localhost:8002/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..c0eef14 --- /dev/null +++ b/admin/src/components/API/Landingpage.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +const Landingpage: React.FC = () => { + return ( + <> +

Übersicht über alle Gegenstände und Ausleihen

+ + ); +}; + +export default Landingpage; From ab93c9959d03296dbe42286c517b47b2d105f289 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Sun, 21 Sep 2025 00:48:28 +0200 Subject: [PATCH 4/6] fullfilled landingpage --- admin/src/components/API/Landingpage.tsx | 204 ++++++++++++++++++++++- backend/routes/apiV2.js | 9 + backend/services/database.js | 10 ++ 3 files changed, 222 insertions(+), 1 deletion(-) diff --git a/admin/src/components/API/Landingpage.tsx b/admin/src/components/API/Landingpage.tsx index c0eef14..a32d66e 100644 --- a/admin/src/components/API/Landingpage.tsx +++ b/admin/src/components/API/Landingpage.tsx @@ -1,9 +1,211 @@ import React from "react"; +import { useEffect } from "react"; +import { useState } from "react"; +import { Spinner, Text, VStack, Box } from "@chakra-ui/react"; +import { Table, Heading } from "@chakra-ui/react"; +import { formatDateTime } from "@/utils/userFuncs"; const Landingpage: React.FC = () => { + const [isLoading, setIsLoading] = useState(false); + const [loans, setLoans] = useState([]); + + useEffect(() => { + setIsLoading(true); + fetch("http://localhost:8002/apiV2/allLoans") + .then((response) => response.json()) + .then((data) => { + setLoans(data); + setIsLoading(false); + }) + .catch((error) => { + console.error("Error fetching loans:", error); + setIsLoading(false); + }); + }, []); + return ( <> -

Übersicht über alle Gegenstände und Ausleihen

+ + Matthias-Claudius-Schule Technik + + + Alle Ausleihen + + {isLoading && ( + + + Loading... + + )} + {!isLoading && ( + + + + + + + + + + + + # + + + Username + + + Start Date + + + End Date + + + Loaned Items + + + Returned Date + + + Take Date + + + + + {loans.map((loan) => ( + + + {loan.id} + + {loan.username} + + {formatDateTime(loan.start_date)} + + + {formatDateTime(loan.end_date)} + + + {loan.loaned_items_name} + + + {formatDateTime(loan.returned_date)} + + + {formatDateTime(loan.take_date)} + + + ))} + + + + + )} ); }; diff --git a/backend/routes/apiV2.js b/backend/routes/apiV2.js index faecec2..1cfb906 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(); @@ -90,4 +91,12 @@ router.post("/setTakeDate/:key/:loan_code", async (req, res) => { } }); +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 8a83a0e..4e1492a 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -447,3 +447,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 }; +}; From 7f5f4648418880bdcd806eed33aa5eb2b8a5f561 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Sun, 21 Sep 2025 10:45:16 +0200 Subject: [PATCH 5/6] changed design for the landingpage --- admin/src/components/API/Landingpage.tsx | 338 ++++++++++------------- 1 file changed, 153 insertions(+), 185 deletions(-) diff --git a/admin/src/components/API/Landingpage.tsx b/admin/src/components/API/Landingpage.tsx index a32d66e..51fd700 100644 --- a/admin/src/components/API/Landingpage.tsx +++ b/admin/src/components/API/Landingpage.tsx @@ -1,210 +1,178 @@ -import React from "react"; -import { useEffect } from "react"; -import { useState } from "react"; -import { Spinner, Text, VStack, Box } from "@chakra-ui/react"; -import { Table, Heading } from "@chakra-ui/react"; +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 [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(() => { - setIsLoading(true); - fetch("http://localhost:8002/apiV2/allLoans") - .then((response) => response.json()) - .then((data) => { - setLoans(data); + 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); - }) - .catch((error) => { - console.error("Error fetching loans:", error); - setIsLoading(false); - }); - }, []); + } + }; + fetchLoans(); + }, [reload]); return ( <> - + Matthias-Claudius-Schule Technik + + {/* Action toolbar */} + + + setReload(!reload)} + > + + + + + {/* End action toolbar */} + Alle Ausleihen + + {isError && ( + + )} + {isLoading && ( Loading... )} + {!isLoading && ( - - - - - - - - - - - - # - - - Username - - - Start Date - - - End Date - - - Loaned Items - - - Returned Date - - - Take Date - - - - - {loans.map((loan) => ( - - - {loan.id} - - {loan.username} - - {formatDateTime(loan.start_date)} - - - {formatDateTime(loan.end_date)} - - - {loan.loaned_items_name} - - - {formatDateTime(loan.returned_date)} - - - {formatDateTime(loan.take_date)} - - - ))} - - - - + + + + + # + + + 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. + )} ); From ea5d31384a9096ba8d5a7ade65e7891ffb1156af Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Sun, 21 Sep 2025 10:45:31 +0200 Subject: [PATCH 6/6] added a bit documentation to the api file --- backend/routes/apiV2.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/routes/apiV2.js b/backend/routes/apiV2.js index 1cfb906..1b81426 100644 --- a/backend/routes/apiV2.js +++ b/backend/routes/apiV2.js @@ -46,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; @@ -59,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; @@ -75,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; @@ -91,6 +92,7 @@ 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) {