diff --git a/FrontendV2/src/App.tsx b/FrontendV2/src/App.tsx index 7435b63..b1d32d2 100644 --- a/FrontendV2/src/App.tsx +++ b/FrontendV2/src/App.tsx @@ -9,6 +9,7 @@ import { useAtom } from "jotai"; import { setIsLoggedInAtom } from "@/states/Atoms"; import { UserContext, type User } from "./states/Context"; import { triggerLogoutAtom } from "@/states/Atoms"; +import { MyLoansPage } from "./pages/MyLoansPage"; const API_BASE = (import.meta as any).env?.VITE_BACKEND_URL || @@ -51,6 +52,7 @@ function App() { }> } /> + } /> } /> diff --git a/FrontendV2/src/components/Header.tsx b/FrontendV2/src/components/Header.tsx new file mode 100644 index 0000000..767b1b3 --- /dev/null +++ b/FrontendV2/src/components/Header.tsx @@ -0,0 +1,99 @@ +import { Badge, Button, Flex, Heading, Stack, Text } from "@chakra-ui/react"; +import Cookies from "js-cookie"; +import { useAtom } from "jotai"; +import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms"; +import { useNavigate } from "react-router-dom"; +import { + CircleUserRound, + RotateCcwKey, + Code, + LifeBuoy, + LogOut, + CalendarPlus, +} from "lucide-react"; +import { useUserContext } from "@/states/Context"; + +export const Header = () => { + const navigate = useNavigate(); + + const userData = useUserContext(); + + const [, setTriggerLogout] = useAtom(triggerLogoutAtom); + const [, setIsLoggedIn] = useAtom(setIsLoggedInAtom); + + return ( + + + + + Home + + + + Willkommen zurück,{" "} + {userData.username.replace( + /^./, + userData.username[0].toUpperCase() + )} + ! + + + Rolle: {userData.role} + + + + + + + + + + + + + + + + + + ); +}; diff --git a/FrontendV2/src/pages/HomePage.tsx b/FrontendV2/src/pages/HomePage.tsx index a227e79..73947ab 100644 --- a/FrontendV2/src/pages/HomePage.tsx +++ b/FrontendV2/src/pages/HomePage.tsx @@ -1,25 +1,20 @@ -import { useUserContext } from "@/states/Context"; import { Container, Stack, - Heading, Text, - Badge, - Flex, Button, Input, Spinner, VStack, Table, } from "@chakra-ui/react"; -import { triggerLogoutAtom, setIsLoggedInAtom } from "@/states/Atoms"; import { useAtom } from "jotai"; -import Cookies from "js-cookie"; import { getBorrowableItems } from "@/utils/Fetcher"; import { useState } from "react"; import MyAlert from "@/components/myChakra/MyAlert"; import { borrowAbleItemsAtom } from "@/states/Atoms"; import { createLoan } from "@/utils/Fetcher"; +import { Header } from "@/components/Header"; export interface User { username: string; @@ -27,9 +22,6 @@ export interface User { } export const HomePage = () => { - const userData = useUserContext(); - const [, setTriggerLogout] = useAtom(triggerLogoutAtom); - const [, setIsLoggedIn] = useAtom(setIsLoggedInAtom); const [borrowableItems, setBorrowableItems] = useAtom(borrowAbleItemsAtom); const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); @@ -52,56 +44,7 @@ export const HomePage = () => { return ( - - - - - Home - - - - Willkommen zurück,{" "} - {userData.username.replace( - /^./, - userData.username[0].toUpperCase() - )} - ! - - - Rolle: {userData.role} - - - - - - - +
{isMsg && ( { + const navigate = useNavigate(); + + const [loans, setLoans] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + // Error handling states + const [isMsg, setIsMsg] = useState(false); + const [msgStatus, setMsgStatus] = useState<"error" | "success">("error"); + const [msgTitle, setMsgTitle] = useState(""); + const [msgDescription, setMsgDescription] = useState(""); + + useEffect(() => { + if (!Cookies.get("token")) { + navigate("/login", { replace: true }); + return; + } + + const fetchLoans = async () => { + try { + setIsLoading(true); + const res = await fetch(`${API_BASE}/api/userLoans`, { + method: "GET", + headers: { + Authorization: `Bearer ${Cookies.get("token")}`, + }, + }); + + if (!res.ok) { + setMsgStatus("error"); + setMsgTitle("Fehler"); + setMsgDescription( + "Beim Laden der Ausleihen ist ein Fehler aufgetreten." + ); + setIsMsg(true); + return; + } + + const data = await res.json(); + setLoans(data); + console.log("Fetched loans:", data); + } catch (e) { + setMsgStatus("error"); + setMsgTitle("Fehler"); + setMsgDescription("Netzwerkfehler beim Laden der Ausleihen."); + setIsMsg(true); + } finally { + setIsLoading(false); + } + }; + + fetchLoans(); + }, [navigate]); + + const deleteLoan = async (loanId: number) => { + try { + const res = await fetch(`${API_BASE}/api/SETdeleteLoan/${loanId}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${Cookies.get("token")}`, + }, + }); + + if (!res.ok) { + setMsgStatus("error"); + setMsgTitle("Fehler"); + setMsgDescription( + "Beim Löschen der Ausleihe ist ein Fehler aufgetreten." + ); + setIsMsg(true); + return; + } + + setLoans((prev) => prev.filter((loan) => loan.id !== loanId)); + setMsgStatus("success"); + setMsgTitle("Erfolg"); + setMsgDescription("Ausleihe erfolgreich gelöscht."); + setIsMsg(true); + } catch (e) { + setMsgStatus("error"); + setMsgTitle("Fehler"); + setMsgDescription("Netzwerkfehler beim Löschen der Ausleihe."); + setIsMsg(true); + } + }; + + const formatDate = (iso: string | null) => { + if (!iso) return "-"; + const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/); + if (!m) return iso; + const [, y, M, d, h, min] = m; + return `${d}.${M}.${y} ${h}:${min}`; + }; + + return ( + <> + +
+ {isMsg && ( + + )} + {isLoading && ( + + + Loading... + + )} + {loans && ( + + + + + + + + + Ausleihcode + Startdatum + Enddatum + Geräte + Ausleihdatum + Rückgabedatum + Aktionen + + + + {loans.map((loan) => ( + + {loan.loan_code} + {formatDate(loan.start_date)} + {formatDate(loan.end_date)} + {loan.loaned_items_name} + {formatDate(loan.take_date)} + {formatDate(loan.returned_date)} + + + + + ))} + + + )} + + + ); +}; diff --git a/backend/routes/api.js b/backend/routes/api.js index 8a97c0a..6a09cba 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -26,6 +26,7 @@ import { createAPIentry, deleteAPKey, getLoanInfoWithID, + SETdeleteLoanFromDatabase, } from "../services/database.js"; import { authenticate, generateToken } from "../services/tokenService.js"; const router = express.Router(); @@ -261,6 +262,16 @@ router.delete("/deleteLoan/:id", authenticate, async (req, res) => { } }); +router.delete("/SETdeleteLoan/:id", authenticate, async (req, res) => { + const loanId = req.params.id; + const result = await SETdeleteLoanFromDatabase(loanId); + if (result.success) { + res.status(200).json({ message: "Loan deleted successfully" }); + } else { + res.status(500).json({ message: "Failed to delete loan" }); + } +}); + router.post("/borrowableItems", authenticate, async (req, res) => { const { startDate, endDate } = req.body || {}; if (!startDate || !endDate) { diff --git a/backend/services/database.js b/backend/services/database.js index 91d0247..b3f39b5 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -126,9 +126,10 @@ export const getLoansFromDatabase = async () => { }; export const getUserLoansFromDatabase = async (username) => { - const [result] = await pool.query("SELECT * FROM loans WHERE username = ?;", [ - username, - ]); + const [result] = await pool.query( + "SELECT * FROM loans WHERE username = ? AND deleted = 0;", + [username] + ); if (result.length > 0) { return { success: true, data: result }; } else if (result.length == 0) { @@ -149,6 +150,18 @@ export const deleteLoanFromDatabase = async (loanId) => { } }; +export const SETdeleteLoanFromDatabase = async (loanId) => { + const [result] = await pool.query( + "UPDATE loans SET deleted = 1 WHERE id = ?;", + [loanId] + ); + if (result.affectedRows > 0) { + return { success: true }; + } else { + return { success: false }; + } +}; + export const getBorrowableItemsFromDatabase = async ( startDate, endDate,