Compare commits
7 Commits
a0be100db5
...
1b08344a0f
Author | SHA1 | Date | |
---|---|---|---|
1b08344a0f | |||
ea5d31384a | |||
7f5f464841 | |||
ab93c9959d | |||
679ef7dcbd | |||
c3572a3d70 | |||
4fc60a08d9 |
@@ -4,9 +4,7 @@ import Layout from "./Layout/Layout";
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Layout>
|
<Layout />
|
||||||
<p></p>
|
|
||||||
</Layout>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -3,15 +3,20 @@ import { useEffect } from "react";
|
|||||||
import Dashboard from "./Dashboard";
|
import Dashboard from "./Dashboard";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import Landingpage from "@/components/API/Landingpage";
|
||||||
|
|
||||||
type LayoutProps = {
|
const Layout: React.FC = () => {
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
const [showAPI, setShowAPI] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const path = window.location.pathname.replace(/\/+$/, ""); // remove trailing slash
|
||||||
|
if (path === "/api") {
|
||||||
|
setShowAPI(true);
|
||||||
|
console.log("signal");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Cookies.get("token")) {
|
if (Cookies.get("token")) {
|
||||||
const verifyToken = async () => {
|
const verifyToken = async () => {
|
||||||
const response = await fetch("https://backend.insta.the1s.de/api/verifyToken", {
|
const response = await fetch("https://backend.insta.the1s.de/api/verifyToken", {
|
||||||
@@ -37,8 +42,15 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
setIsLoggedIn(false);
|
setIsLoggedIn(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (showAPI) {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<Landingpage />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<main>
|
<main>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<Dashboard onLogout={() => handleLogout()} />
|
<Dashboard onLogout={() => handleLogout()} />
|
||||||
@@ -46,8 +58,6 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
<Login onSuccess={() => setIsLoggedIn(true)} />
|
<Login onSuccess={() => setIsLoggedIn(true)} />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
181
admin/src/components/API/Landingpage.tsx
Normal file
181
admin/src/components/API/Landingpage.tsx
Normal file
@@ -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<Loan[]>([]);
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Heading as="h1" size="lg" mb={2}>
|
||||||
|
Matthias-Claudius-Schule Technik
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
{/* Action toolbar */}
|
||||||
|
<HStack
|
||||||
|
mb={4}
|
||||||
|
gap={3}
|
||||||
|
justify="flex-start"
|
||||||
|
align="center"
|
||||||
|
flexWrap="wrap"
|
||||||
|
>
|
||||||
|
<Tooltip content="Ausleihen neu laden" openDelay={300}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Refresh loans"
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
rounded="md"
|
||||||
|
shadow="sm"
|
||||||
|
_hover={{ shadow: "md", transform: "translateY(-2px)" }}
|
||||||
|
_active={{ transform: "translateY(0)" }}
|
||||||
|
onClick={() => setReload(!reload)}
|
||||||
|
>
|
||||||
|
<RefreshCcwDot size={18} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</HStack>
|
||||||
|
{/* End action toolbar */}
|
||||||
|
|
||||||
|
<Heading as="h2" size="md" mb={4}>
|
||||||
|
Alle 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>Benutzername</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Startdatum</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Enddatum</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Ausgeliehene Artikel</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Rückgabedatum</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Ausleihdatum</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
{loans.map((loan) => (
|
||||||
|
<Table.Row key={loan.id}>
|
||||||
|
<Table.Cell>{loan.id}</Table.Cell>
|
||||||
|
<Table.Cell>{loan.username}</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(loan.start_date)}</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(loan.end_date)}</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{Array.isArray(loan.loaned_items_name)
|
||||||
|
? loan.loaned_items_name.join(", ")
|
||||||
|
: loan.loaned_items_name}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(loan.returned_date)}</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(loan.take_date)}</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
))}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && loans.length === 0 && !isError && (
|
||||||
|
<Text color="gray.500" mt={2}>
|
||||||
|
Keine Ausleihen vorhanden.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Landingpage;
|
@@ -9,7 +9,6 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Heading,
|
Heading,
|
||||||
Icon,
|
Icon,
|
||||||
Tag,
|
|
||||||
Input,
|
Input,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Tooltip } from "@/components/ui/tooltip";
|
import { Tooltip } from "@/components/ui/tooltip";
|
||||||
@@ -219,57 +218,34 @@ const ItemTable: React.FC = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSafeState(item.id).then(() => setReload(!reload))
|
changeSafeState(item.id).then(() => setReload(!reload))
|
||||||
}
|
}
|
||||||
size="sm"
|
size="xs"
|
||||||
>
|
|
||||||
{item.inSafe ? (
|
|
||||||
<Tag.Root
|
|
||||||
size="md"
|
|
||||||
bg="green.500"
|
|
||||||
color="white"
|
|
||||||
px={4}
|
|
||||||
py={1.5}
|
|
||||||
rounded="full"
|
rounded="full"
|
||||||
display="inline-flex"
|
px={3}
|
||||||
alignItems="center"
|
py={1}
|
||||||
gap={2}
|
gap={2}
|
||||||
shadow="sm"
|
variant="ghost"
|
||||||
_hover={{ shadow: "md" }}
|
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"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Icon as={CheckCircle2} boxSize={4} />
|
<Icon
|
||||||
<Text
|
as={item.inSafe ? CheckCircle2 : XCircle}
|
||||||
as="span"
|
boxSize={3.5}
|
||||||
fontSize="xs"
|
mr={2}
|
||||||
letterSpacing="wide"
|
/>
|
||||||
textTransform="uppercase"
|
<Text as="span" fontSize="xs" fontWeight="semibold">
|
||||||
>
|
{item.inSafe ? "Yes" : "No"}
|
||||||
Yes
|
|
||||||
</Text>
|
</Text>
|
||||||
</Tag.Root>
|
|
||||||
) : (
|
|
||||||
<Tag.Root
|
|
||||||
size="md"
|
|
||||||
bg="red.500"
|
|
||||||
color="white"
|
|
||||||
px={4}
|
|
||||||
py={1.5}
|
|
||||||
rounded="full"
|
|
||||||
display="inline-flex"
|
|
||||||
alignItems="center"
|
|
||||||
gap={2}
|
|
||||||
shadow="sm"
|
|
||||||
_hover={{ shadow: "md" }}
|
|
||||||
>
|
|
||||||
<Icon as={XCircle} boxSize={4} />
|
|
||||||
<Text
|
|
||||||
as="span"
|
|
||||||
fontSize="xs"
|
|
||||||
letterSpacing="wide"
|
|
||||||
textTransform="uppercase"
|
|
||||||
>
|
|
||||||
No
|
|
||||||
</Text>
|
|
||||||
</Tag.Root>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{formatDateTime(item.entry_created_at)}</Table.Cell>
|
<Table.Cell>{formatDateTime(item.entry_created_at)}</Table.Cell>
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
setReturnDateV2,
|
setReturnDateV2,
|
||||||
setTakeDateV2,
|
setTakeDateV2,
|
||||||
getLoanByCodeV2,
|
getLoanByCodeV2,
|
||||||
|
getAllLoansV2,
|
||||||
} from "../services/database.js";
|
} from "../services/database.js";
|
||||||
|
|
||||||
dotenv.config();
|
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) => {
|
router.get("/getLoanByCode/:key/:loan_code", async (req, res) => {
|
||||||
if (req.params.key === process.env.ADMIN_ID) {
|
if (req.params.key === process.env.ADMIN_ID) {
|
||||||
const loan_code = req.params.loan_code;
|
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) => {
|
router.post("/setReturnDate/:key/:loan_code", async (req, res) => {
|
||||||
if (req.params.key === process.env.ADMIN_ID) {
|
if (req.params.key === process.env.ADMIN_ID) {
|
||||||
const loanCode = req.params.loan_code;
|
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) => {
|
router.post("/setTakeDate/:key/:loan_code", async (req, res) => {
|
||||||
if (req.params.key === process.env.ADMIN_ID) {
|
if (req.params.key === process.env.ADMIN_ID) {
|
||||||
const loanCode = req.params.loan_code;
|
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;
|
export default router;
|
||||||
|
@@ -448,3 +448,13 @@ export const updateItemByID = async (itemId, item_name, can_borrow_role) => {
|
|||||||
if (result.affectedRows > 0) return { success: true };
|
if (result.affectedRows > 0) return { success: true };
|
||||||
return { success: false };
|
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 };
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user