Fix routing and update translations: change landing page route to '/landingpage', update user info dialog, and enhance localization strings.
This commit is contained in:
@@ -79,7 +79,7 @@ function App() {
|
|||||||
<Route element={<ProtectedRoutes />}>
|
<Route element={<ProtectedRoutes />}>
|
||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/my-loans" element={<MyLoansPage />} />
|
<Route path="/my-loans" element={<MyLoansPage />} />
|
||||||
<Route path="/landing" element={<Landingpage />} />
|
<Route path="/landingpage" element={<Landingpage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Avatar,
|
Avatar,
|
||||||
Card,
|
Card,
|
||||||
|
Grid,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { PasswordInput } from "@/components/ui/password-input";
|
import { PasswordInput } from "@/components/ui/password-input";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
@@ -28,7 +28,8 @@ import {
|
|||||||
LogOut,
|
LogOut,
|
||||||
CalendarPlus,
|
CalendarPlus,
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
Flag,
|
Languages,
|
||||||
|
Table,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useUserContext } from "@/states/Context";
|
import { useUserContext } from "@/states/Context";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -97,6 +98,18 @@ export const Header = () => {
|
|||||||
|
|
||||||
const username = userData.first_name ? userData.first_name : "N/A";
|
const username = userData.first_name ? userData.first_name : "N/A";
|
||||||
const fullname = userData.first_name + " " + userData.last_name;
|
const fullname = userData.first_name + " " + userData.last_name;
|
||||||
|
const randomColor = [
|
||||||
|
"gray",
|
||||||
|
"red",
|
||||||
|
"orange",
|
||||||
|
"yellow",
|
||||||
|
"green",
|
||||||
|
"teal",
|
||||||
|
"blue",
|
||||||
|
"cyan",
|
||||||
|
"purple",
|
||||||
|
"pink",
|
||||||
|
];
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
Cookies.remove("token");
|
Cookies.remove("token");
|
||||||
@@ -158,12 +171,12 @@ export const Header = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
value="change-password"
|
value="landingpage"
|
||||||
onSelect={() => setPwOpen(true)}
|
onSelect={() => navigate("/landingpage", { replace: true })}
|
||||||
children={
|
children={
|
||||||
<HStack gap={3}>
|
<HStack gap={3}>
|
||||||
<RotateCcwKey size={16} />
|
<Table size={16} />
|
||||||
<Text as="span">{t("change-password")}</Text>
|
<Text as="span">{t("landingpage")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -177,7 +190,7 @@ export const Header = () => {
|
|||||||
}}
|
}}
|
||||||
children={
|
children={
|
||||||
<HStack gap={3}>
|
<HStack gap={3}>
|
||||||
<LifeBuoy size={16} />
|
<Languages size={16} />
|
||||||
<Text as="span">{t("change-language")}</Text>
|
<Text as="span">{t("change-language")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
}
|
}
|
||||||
@@ -244,7 +257,7 @@ export const Header = () => {
|
|||||||
size="2xl"
|
size="2xl"
|
||||||
className="tracking-tight text-slate-900 dark:text-slate-100"
|
className="tracking-tight text-slate-900 dark:text-slate-100"
|
||||||
>
|
>
|
||||||
Home
|
{t("app-title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -253,13 +266,11 @@ export const Header = () => {
|
|||||||
{t("greeting")}
|
{t("greeting")}
|
||||||
<strong>{username}</strong>!
|
<strong>{username}</strong>!
|
||||||
</Text>
|
</Text>
|
||||||
<Badge variant="subtle" px={2} py={1} borderRadius="full">
|
|
||||||
Rolle: {userData?.role ?? "—"}
|
|
||||||
</Badge>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<HStack>
|
{/* Avatar: visible on mobile, hidden on desktop (desktop version is in the actions bar) */}
|
||||||
|
<HStack display={{ base: "flex", md: "none" }}>
|
||||||
<Avatar.Root>
|
<Avatar.Root>
|
||||||
<button
|
<button
|
||||||
onClick={() => setUserDialog(true)}
|
onClick={() => setUserDialog(true)}
|
||||||
@@ -279,6 +290,18 @@ export const Header = () => {
|
|||||||
flexWrap="wrap"
|
flexWrap="wrap"
|
||||||
display={{ base: "none", md: "flex" }}
|
display={{ base: "none", md: "flex" }}
|
||||||
>
|
>
|
||||||
|
{/* Desktop avatar, aligned with action buttons */}
|
||||||
|
<Avatar.Root
|
||||||
|
colorPalette={randomColor[Math.floor(Math.random() * 10)]}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setUserDialog(true)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<Avatar.Fallback name={fullname} />
|
||||||
|
</button>
|
||||||
|
</Avatar.Root>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
colorScheme="teal"
|
colorScheme="teal"
|
||||||
onClick={() => navigate("/", { replace: true })}
|
onClick={() => navigate("/", { replace: true })}
|
||||||
@@ -296,10 +319,10 @@ export const Header = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button variant="ghost" onClick={() => setPwOpen(true)}>
|
<Button onClick={() => navigate("/landingpage", { replace: true })}>
|
||||||
<HStack gap={2}>
|
<HStack gap={2}>
|
||||||
<RotateCcwKey size={18} />
|
<Table size={18} />
|
||||||
<Text as="span">{t("change-password")}</Text>
|
<Text as="span">{t("landingpage")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -313,7 +336,7 @@ export const Header = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HStack gap={2}>
|
<HStack gap={2}>
|
||||||
<Flag size={18} />
|
<Languages size={18} />
|
||||||
<Text as="span">{t("change-language")}</Text>
|
<Text as="span">{t("change-language")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -351,48 +374,84 @@ export const Header = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{/* User Info Dialoge */}
|
||||||
{userDialog && (
|
{userDialog && (
|
||||||
<Card.Root maxW="sm">
|
<Flex
|
||||||
<Card.Header>
|
position="fixed"
|
||||||
<Card.Title>
|
inset={0}
|
||||||
{" "}
|
zIndex={1000}
|
||||||
<Avatar.Root>
|
align="center"
|
||||||
<button
|
justify="center"
|
||||||
onClick={() => setUserDialog(true)}
|
bg="blackAlpha.400"
|
||||||
style={{ cursor: "pointer" }}
|
backdropFilter="blur(6px)"
|
||||||
>
|
>
|
||||||
<Avatar.Fallback name={fullname} />
|
<Card.Root maxW="sm" w="full" mx={4}>
|
||||||
</button>
|
<Card.Header>
|
||||||
</Avatar.Root>
|
<Card.Title>
|
||||||
</Card.Title>
|
<Flex justify="center" align="center" w="100%">
|
||||||
<Card.Description>{t("user-info-desc")}</Card.Description>
|
<Avatar.Root
|
||||||
</Card.Header>
|
size={"2xl"}
|
||||||
<Card.Body>
|
colorPalette={randomColor[Math.floor(Math.random() * 10)]}
|
||||||
<Stack gap="4" w="full">
|
>
|
||||||
<Text>
|
<Avatar.Fallback name={fullname} />
|
||||||
<strong>{t("first-name")}:</strong> {userData.first_name}
|
</Avatar.Root>
|
||||||
</Text>
|
</Flex>
|
||||||
<Text>
|
</Card.Title>
|
||||||
<strong>{t("last-name")}:</strong> {userData.last_name}
|
<Card.Description>{t("user-info-desc")}</Card.Description>
|
||||||
</Text>
|
</Card.Header>
|
||||||
<Text>
|
<Card.Body>
|
||||||
<strong>{t("username")}:</strong> {userData.username}
|
<Stack gap="4" w="full">
|
||||||
</Text>
|
<Box as="dl">
|
||||||
<Text>
|
<Grid
|
||||||
<strong>{t("role")}:</strong> {userData.role}
|
templateColumns="auto 1fr"
|
||||||
</Text>
|
rowGap={2}
|
||||||
<Text>
|
columnGap={4}
|
||||||
<strong>{t("admin-status")}:</strong>{" "}
|
alignItems="start"
|
||||||
{userData.is_admin ? t("yes") : t("no")}
|
>
|
||||||
</Text>
|
<Text as="dt" fontWeight="bold" textAlign="left">
|
||||||
</Stack>
|
{t("first-name")}:
|
||||||
</Card.Body>
|
</Text>
|
||||||
<Card.Footer justifyContent="flex-end">
|
<Text as="dd">{userData.first_name}</Text>
|
||||||
<Button variant="outline" onClick={() => setUserDialog(false)}>
|
|
||||||
{t("cancel")}
|
<Text as="dt" fontWeight="bold" textAlign="left">
|
||||||
</Button>
|
{t("last-name")}:
|
||||||
</Card.Footer>
|
</Text>
|
||||||
</Card.Root>
|
<Text as="dd">{userData.last_name}</Text>
|
||||||
|
|
||||||
|
<Text as="dt" fontWeight="bold" textAlign="left">
|
||||||
|
{t("username")}:
|
||||||
|
</Text>
|
||||||
|
<Text as="dd">{userData.username}</Text>
|
||||||
|
|
||||||
|
<Text as="dt" fontWeight="bold" textAlign="left">
|
||||||
|
{t("role")}:
|
||||||
|
</Text>
|
||||||
|
<Text as="dd">{userData.role}</Text>
|
||||||
|
|
||||||
|
<Text as="dt" fontWeight="bold" textAlign="left">
|
||||||
|
{t("admin-status")}:
|
||||||
|
</Text>
|
||||||
|
<Text as="dd">
|
||||||
|
{userData.is_admin ? t("yes") : t("no")}
|
||||||
|
</Text>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button variant="solid" onClick={() => setPwOpen(true)}>
|
||||||
|
<HStack gap={2}>
|
||||||
|
<RotateCcwKey size={18} />
|
||||||
|
<Text as="span">{t("change-password")}</Text>
|
||||||
|
</HStack>
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Card.Body>
|
||||||
|
<Card.Footer justifyContent="flex-end">
|
||||||
|
<Button variant="outline" onClick={() => setUserDialog(false)}>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</Card.Footer>
|
||||||
|
</Card.Root>
|
||||||
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Passwort-Dialog (kontrolliert) */}
|
{/* Passwort-Dialog (kontrolliert) */}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import {
|
|||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Button,
|
Button,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Lock, LockOpen } from "lucide-react";
|
|
||||||
import MyAlert from "@/components/myChakra/MyAlert";
|
import MyAlert from "@/components/myChakra/MyAlert";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { API_BASE } from "@/config/api.config";
|
import { API_BASE } from "@/config/api.config";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export const formatDateTime = (value: string | null | undefined) => {
|
export const formatDateTime = (value: string | null | undefined) => {
|
||||||
if (!value) return "N/A";
|
if (!value) return "N/A";
|
||||||
@@ -38,7 +38,7 @@ type Device = {
|
|||||||
id: number;
|
id: number;
|
||||||
item_name: string;
|
item_name: string;
|
||||||
can_borrow_role: string;
|
can_borrow_role: string;
|
||||||
inSafe: number;
|
in_safe: number;
|
||||||
entry_created_at: string;
|
entry_created_at: string;
|
||||||
last_borrowed_person: string | null;
|
last_borrowed_person: string | null;
|
||||||
currently_borrowing: string | null;
|
currently_borrowing: string | null;
|
||||||
@@ -46,6 +46,7 @@ type Device = {
|
|||||||
|
|
||||||
const Landingpage: React.FC = () => {
|
const Landingpage: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [loans, setLoans] = useState<Loan[]>([]);
|
const [loans, setLoans] = useState<Loan[]>([]);
|
||||||
@@ -119,6 +120,10 @@ const Landingpage: React.FC = () => {
|
|||||||
Matthias-Claudius-Schule Technik
|
Matthias-Claudius-Schule Technik
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
|
<Button onClick={() => navigate("/", { replace: true })}>
|
||||||
|
{t("back")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Heading as="h2" size="md" mb={4}>
|
<Heading as="h2" size="md" mb={4}>
|
||||||
{t("all-loans")}
|
{t("all-loans")}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -158,10 +163,10 @@ const Landingpage: React.FC = () => {
|
|||||||
<strong>{t("rented-items")}</strong>
|
<strong>{t("rented-items")}</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>{t("return-date")}</strong>
|
<strong>{t("take-date")}</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>{t("take-date")}</strong>
|
<strong>{t("return-date")}</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
@@ -177,8 +182,8 @@ const Landingpage: React.FC = () => {
|
|||||||
? loan.loaned_items_name.join(", ")
|
? loan.loaned_items_name.join(", ")
|
||||||
: loan.loaned_items_name}
|
: loan.loaned_items_name}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{formatDateTime(loan.returned_date)}</Table.Cell>
|
|
||||||
<Table.Cell>{formatDateTime(loan.take_date)}</Table.Cell>
|
<Table.Cell>{formatDateTime(loan.take_date)}</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(loan.returned_date)}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
))}
|
))}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
@@ -201,24 +206,25 @@ const Landingpage: React.FC = () => {
|
|||||||
<Card.Root
|
<Card.Root
|
||||||
key={device.id}
|
key={device.id}
|
||||||
size="sm"
|
size="sm"
|
||||||
bg={device.inSafe ? "green" : "red"}
|
bg={device.in_safe ? "green" : "red"}
|
||||||
h="full"
|
h="full"
|
||||||
minH="100px"
|
minH="100px"
|
||||||
>
|
>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
{device.inSafe ? <LockOpen size={16} /> : <Lock size={16} />}
|
<Heading size="md">
|
||||||
<Heading size="md">{device.item_name}</Heading>
|
<strong>{device.item_name}</strong>
|
||||||
|
</Heading>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Body color="fg.muted">
|
<Card.Body>
|
||||||
<Text>
|
<Text>
|
||||||
{t("rent-role")}: {device.can_borrow_role}
|
<strong>{t("role")}</strong>: {device.can_borrow_role}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{t("last-borrowed-person")}:{" "}
|
<strong>{t("last-borrowed-person")}</strong>:{" "}
|
||||||
{device.last_borrowed_person || "N/A"}
|
{device.last_borrowed_person || "N/A"}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{t("currently-borrowed-by")}:{" "}
|
<strong>{t("currently-borrowed-by")}</strong>:{" "}
|
||||||
{device.currently_borrowing || "N/A"}
|
{device.currently_borrowing || "N/A"}
|
||||||
</Text>
|
</Text>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
@@ -238,7 +244,6 @@ const Landingpage: React.FC = () => {
|
|||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
<HStack gap={2}>
|
<HStack gap={2}>
|
||||||
<LockOpen size={16} />
|
|
||||||
<Text>{t("in-locker")}</Text>
|
<Text>{t("in-locker")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -251,7 +256,6 @@ const Landingpage: React.FC = () => {
|
|||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
<HStack gap={2}>
|
<HStack gap={2}>
|
||||||
<Lock size={16} />
|
|
||||||
<Text>{t("not-in-locker")}</Text>
|
<Text>{t("not-in-locker")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -62,5 +62,15 @@
|
|||||||
"change-language": "Sprache ändern",
|
"change-language": "Sprache ändern",
|
||||||
"timezone-info": "Die angezeigten Daten und Uhrzeiten werden in deutscher Zeitzone dargestellt und müssen auch so eingegeben werden.",
|
"timezone-info": "Die angezeigten Daten und Uhrzeiten werden in deutscher Zeitzone dargestellt und müssen auch so eingegeben werden.",
|
||||||
"optional-note": "Optionale Notiz",
|
"optional-note": "Optionale Notiz",
|
||||||
"note": "Notiz"
|
"note": "Notiz",
|
||||||
|
"user-info-desc": "Hier können Sie Ihre persönlichen Informationen einsehen und ändern.",
|
||||||
|
"role": "Rolle",
|
||||||
|
"admin-status": "Admin-Status",
|
||||||
|
"first-name": "Vorname",
|
||||||
|
"last-name": "Nachname",
|
||||||
|
"app-title": "Ausleihsystem",
|
||||||
|
"last-borrowed-person": "Zuletzt ausgeliehen von",
|
||||||
|
"currently-borrowed-by": "Derzeit ausgeliehen von",
|
||||||
|
"back": "Zurückgehen",
|
||||||
|
"landingpage": "Übersichtsseite"
|
||||||
}
|
}
|
||||||
@@ -62,5 +62,15 @@
|
|||||||
"change-language": "Change language",
|
"change-language": "Change language",
|
||||||
"timezone-info": "The displayed dates and times are shown in Berlin timezone and must also be entered as such.",
|
"timezone-info": "The displayed dates and times are shown in Berlin timezone and must also be entered as such.",
|
||||||
"optional-note": "Optional note",
|
"optional-note": "Optional note",
|
||||||
"note": "Note"
|
"note": "Note",
|
||||||
|
"user-info-desc": "Here you can view and edit your personal information.",
|
||||||
|
"role": "Role",
|
||||||
|
"admin-status": "Admin status",
|
||||||
|
"first-name": "First name",
|
||||||
|
"last-name": "Last name",
|
||||||
|
"app-title": "Borrow System",
|
||||||
|
"last-borrowed-person": "Last borrowed by",
|
||||||
|
"currently-borrowed-by": "Currently borrowed by",
|
||||||
|
"back": "Go back",
|
||||||
|
"landingpage": "Overview page"
|
||||||
}
|
}
|
||||||
@@ -48,19 +48,19 @@ services:
|
|||||||
- mysql_v2
|
- mysql_v2
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
mysql:
|
# mysql:
|
||||||
container_name: borrow_system-mysql
|
# container_name: borrow_system-mysql
|
||||||
image: mysql:8.0
|
# image: mysql:8.0
|
||||||
restart: unless-stopped
|
# restart: unless-stopped
|
||||||
environment:
|
# environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
|
# MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
|
||||||
MYSQL_DATABASE: borrow_system
|
# MYSQL_DATABASE: borrow_system
|
||||||
TZ: Europe/Berlin
|
# TZ: Europe/Berlin
|
||||||
volumes:
|
# volumes:
|
||||||
- mysql-data:/var/lib/mysql
|
# - mysql-data:/var/lib/mysql
|
||||||
- ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro
|
# - ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro
|
||||||
ports:
|
# ports:
|
||||||
- "3309:3306"
|
# - "3309:3306"
|
||||||
|
|
||||||
mysql_v2:
|
mysql_v2:
|
||||||
container_name: borrow_system-mysql-v2
|
container_name: borrow_system-mysql-v2
|
||||||
|
|||||||
Reference in New Issue
Block a user