Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fdb54b832b | |||
| 2dede62e37 | |||
| 2cc5545ea8 | |||
| f8e29dca10 | |||
| e52fc13da4 | |||
| 34f133f94c | |||
| 98834a9270 | |||
| 8d04465705 | |||
| 8589971dc8 | |||
| 6ec8e19737 | |||
| 9f44a4796d | |||
| dc0a68f7f1 | |||
| fe3a06e5ce | |||
| 179f5686d1 | |||
| 28373e0231 |
@@ -47,7 +47,7 @@ const Dashboard: React.FC<DashboardProps> = ({ onLogout }) => {
|
||||
viewAPI={() => setActiveView("API")}
|
||||
viewConfig={() => setActiveView("Server Konfiguration")}
|
||||
/>
|
||||
<Box flex="1" display="flex" flexDirection="column">
|
||||
<Box flex="1" display="flex" flexDirection="column" minH={0}>
|
||||
<Flex
|
||||
as="header"
|
||||
align="center"
|
||||
@@ -68,7 +68,7 @@ const Dashboard: React.FC<DashboardProps> = ({ onLogout }) => {
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box as="main" flex="1" p={6}>
|
||||
<Box as="main" flex="1" p={6} minH={0} overflow="hidden">
|
||||
{activeView === "" && (
|
||||
<Flex
|
||||
align="center"
|
||||
|
||||
+228
-184
@@ -57,32 +57,32 @@ const ItemTable: React.FC = () => {
|
||||
|
||||
const handleItemNameChange = (id: number, value: string) => {
|
||||
setItems((prev) =>
|
||||
prev.map((it) => (it.id === id ? { ...it, item_name: value } : it))
|
||||
prev.map((it) => (it.id === id ? { ...it, item_name: value } : it)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleCanBorrowRoleChange = (id: number, value: string) => {
|
||||
setItems((prev) =>
|
||||
prev.map((it) => (it.id === id ? { ...it, can_borrow_role: value } : it))
|
||||
prev.map((it) => (it.id === id ? { ...it, can_borrow_role: value } : it)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleLockerNumberChange = (id: number, value: string) => {
|
||||
setItems((prev) =>
|
||||
prev.map((it) => (it.id === id ? { ...it, safe_nr: value } : it))
|
||||
prev.map((it) => (it.id === id ? { ...it, safe_nr: value } : it)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleDoorKeyChange = (id: number, value: string) => {
|
||||
setItems((prev) =>
|
||||
prev.map((it) => (it.id === id ? { ...it, door_key: value } : it))
|
||||
prev.map((it) => (it.id === id ? { ...it, door_key: value } : it)),
|
||||
);
|
||||
};
|
||||
|
||||
const setError = (
|
||||
status: "error" | "success",
|
||||
message: string,
|
||||
description: string
|
||||
description: string,
|
||||
) => {
|
||||
setIsError(false);
|
||||
setErrorStatus(status);
|
||||
@@ -102,7 +102,7 @@ const ItemTable: React.FC = () => {
|
||||
headers: {
|
||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
@@ -193,185 +193,229 @@ const ItemTable: React.FC = () => {
|
||||
|
||||
{/* make table fill available width, like UserTable */}
|
||||
{!isLoading && (
|
||||
<Table.Root
|
||||
size="sm"
|
||||
striped
|
||||
w="100%"
|
||||
style={{ tableLayout: "auto" }} // Spalten nach Content
|
||||
>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader>
|
||||
<strong>#</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Gegenstand</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausleih Berechtigung</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Im Schließfach</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader width="1%" whiteSpace="nowrap">
|
||||
<strong>Schließfachnummer</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader width="1%" whiteSpace="nowrap">
|
||||
<strong>Schlüssel</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Eintrag erstellt am</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Eintrag aktualisiert am</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>LaP *</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Dav **</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader width="1%" whiteSpace="nowrap">
|
||||
<strong>Aktionen</strong>
|
||||
</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{items.map((item) => (
|
||||
<Table.Row key={item.id}>
|
||||
<Table.Cell>{item.id}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleItemNameChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.item_name}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleCanBorrowRoleChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.can_borrow_role}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
changeSafeState(item.id).then(() => setReload(!reload))
|
||||
}
|
||||
size="xs"
|
||||
rounded="full"
|
||||
px={3}
|
||||
py={1}
|
||||
gap={2}
|
||||
variant="ghost"
|
||||
color={item.in_safe ? "green.600" : "red.600"}
|
||||
borderWidth="1px"
|
||||
borderColor={item.in_safe ? "green.300" : "red.300"}
|
||||
_hover={{
|
||||
bg: item.in_safe ? "green.50" : "red.50",
|
||||
borderColor: item.in_safe ? "green.400" : "red.400",
|
||||
transform: "translateY(-1px)",
|
||||
shadow: "sm",
|
||||
}}
|
||||
_active={{ transform: "translateY(0)" }}
|
||||
aria-label={
|
||||
item.in_safe ? "Mark as not in safe" : "Mark as in safe"
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
as={item.in_safe ? CheckCircle2 : XCircle}
|
||||
boxSize={3.5}
|
||||
mr={2}
|
||||
/>
|
||||
<Text as="span" fontSize="xs" fontWeight="semibold">
|
||||
{item.in_safe ? "Yes" : "No"}
|
||||
</Text>
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleLockerNumberChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.safe_nr}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleDoorKeyChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.door_key}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.entry_created_at)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.entry_updated_at)}</Table.Cell>
|
||||
<Table.Cell>{item.last_borrowed_person}</Table.Cell>
|
||||
<Table.Cell>{item.currently_borrowing}</Table.Cell>
|
||||
<Table.Cell whiteSpace="nowrap">
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleEditItems(
|
||||
item.id,
|
||||
item.item_name,
|
||||
item.safe_nr,
|
||||
item.door_key,
|
||||
item.can_borrow_role
|
||||
).then((response) => {
|
||||
if (response.success) {
|
||||
setError(
|
||||
"success",
|
||||
"Gegenstand erfolgreich bearbeitet!",
|
||||
"Gegenstand " +
|
||||
'"' +
|
||||
item.item_name +
|
||||
'" mit ID ' +
|
||||
item.id +
|
||||
" bearbeitet."
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
colorPalette="teal"
|
||||
size="sm"
|
||||
>
|
||||
<Save />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
deleteItem(item.id).then((response) => {
|
||||
if (response.success) {
|
||||
setItems(items.filter((i) => i.id !== item.id));
|
||||
setError(
|
||||
"success",
|
||||
"Gegenstand gelöscht",
|
||||
"Der Gegenstand wurde erfolgreich gelöscht."
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
colorPalette="red"
|
||||
size="sm"
|
||||
ml={2}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.ScrollArea flex="1" minH={0} rounded="md" mt={4}>
|
||||
<Table.Root
|
||||
size="sm"
|
||||
striped
|
||||
stickyHeader
|
||||
css={{
|
||||
"& [data-sticky]": {
|
||||
position: "sticky",
|
||||
zIndex: 1,
|
||||
bg: "bg",
|
||||
|
||||
_after: {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
top: "0",
|
||||
bottom: "-1px",
|
||||
width: "32px",
|
||||
},
|
||||
},
|
||||
|
||||
"& [data-sticky=end]": {
|
||||
_after: {
|
||||
insetInlineEnd: "0",
|
||||
translate: "100% 0",
|
||||
shadow: "inset 8px 0px 8px -8px rgba(0, 0, 0, 0.16)",
|
||||
},
|
||||
},
|
||||
|
||||
"& [data-sticky=start]": {
|
||||
_after: {
|
||||
insetInlineStart: "0",
|
||||
translate: "-100% 0",
|
||||
shadow: "inset -8px 0px 8px -8px rgba(0, 0, 0, 0.16)",
|
||||
},
|
||||
},
|
||||
|
||||
"& thead tr": {
|
||||
shadow: "0 1px 0 0 {colors.border}",
|
||||
"&:has(th[data-sticky])": {
|
||||
zIndex: 2,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader>
|
||||
<strong>#</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Gegenstand</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausleih Berechtigung</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Im Schließfach</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader width="1%" whiteSpace="nowrap">
|
||||
<strong>Schließfachnummer</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader width="1%" whiteSpace="nowrap">
|
||||
<strong>Schlüssel</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Eintrag erstellt am</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Eintrag aktualisiert am</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>LaP *</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Dav **</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader width="1%" whiteSpace="nowrap">
|
||||
<strong>Aktionen</strong>
|
||||
</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{items.map((item) => (
|
||||
<Table.Row key={item.id}>
|
||||
<Table.Cell>{item.id}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleItemNameChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.item_name}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleCanBorrowRoleChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.can_borrow_role}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
changeSafeState(item.id).then(() => setReload(!reload))
|
||||
}
|
||||
size="xs"
|
||||
rounded="full"
|
||||
px={3}
|
||||
py={1}
|
||||
gap={2}
|
||||
variant="ghost"
|
||||
color={item.in_safe ? "green.600" : "red.600"}
|
||||
borderWidth="1px"
|
||||
borderColor={item.in_safe ? "green.300" : "red.300"}
|
||||
_hover={{
|
||||
bg: item.in_safe ? "green.50" : "red.50",
|
||||
borderColor: item.in_safe ? "green.400" : "red.400",
|
||||
transform: "translateY(-1px)",
|
||||
shadow: "sm",
|
||||
}}
|
||||
_active={{ transform: "translateY(0)" }}
|
||||
aria-label={
|
||||
item.in_safe ? "Mark as not in safe" : "Mark as in safe"
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
as={item.in_safe ? CheckCircle2 : XCircle}
|
||||
boxSize={3.5}
|
||||
mr={2}
|
||||
/>
|
||||
<Text as="span" fontSize="xs" fontWeight="semibold">
|
||||
{item.in_safe ? "Yes" : "No"}
|
||||
</Text>
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleLockerNumberChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.safe_nr}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
size="sm"
|
||||
w="max-content"
|
||||
onChange={(e) =>
|
||||
handleDoorKeyChange(item.id, e.target.value)
|
||||
}
|
||||
value={item.door_key}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{formatDateTime(item.entry_created_at)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{formatDateTime(item.entry_updated_at)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{item.last_borrowed_person}</Table.Cell>
|
||||
<Table.Cell>{item.currently_borrowing}</Table.Cell>
|
||||
<Table.Cell whiteSpace="nowrap">
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleEditItems(
|
||||
item.id,
|
||||
item.item_name,
|
||||
item.safe_nr,
|
||||
item.door_key,
|
||||
item.can_borrow_role,
|
||||
).then((response) => {
|
||||
if (response.success) {
|
||||
setError(
|
||||
"success",
|
||||
"Gegenstand erfolgreich bearbeitet!",
|
||||
"Gegenstand " +
|
||||
'"' +
|
||||
item.item_name +
|
||||
'" mit ID ' +
|
||||
item.id +
|
||||
" bearbeitet.",
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
colorPalette="teal"
|
||||
size="sm"
|
||||
>
|
||||
<Save />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
deleteItem(item.id).then((response) => {
|
||||
if (response.success) {
|
||||
setItems(items.filter((i) => i.id !== item.id));
|
||||
setError(
|
||||
"success",
|
||||
"Gegenstand gelöscht",
|
||||
"Der Gegenstand wurde erfolgreich gelöscht.",
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
colorPalette="red"
|
||||
size="sm"
|
||||
ml={2}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.ScrollArea>
|
||||
)}
|
||||
<Text>* LaP = Letzte ausleihende Person</Text>
|
||||
<Text>** Dav = Derzeit ausgeliehen von</Text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
Table,
|
||||
Spinner,
|
||||
Text,
|
||||
@@ -31,7 +32,7 @@ const LoanTable: React.FC = () => {
|
||||
const setError = (
|
||||
status: "error" | "success",
|
||||
message: string,
|
||||
description: string
|
||||
description: string,
|
||||
) => {
|
||||
setIsError(false);
|
||||
setErrorStatus(status);
|
||||
@@ -65,7 +66,7 @@ const LoanTable: React.FC = () => {
|
||||
headers: {
|
||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
@@ -83,7 +84,7 @@ const LoanTable: React.FC = () => {
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box h="full" display="flex" flexDirection="column" minH={0}>
|
||||
{/* Action toolbar */}
|
||||
<HStack
|
||||
mb={4}
|
||||
@@ -131,86 +132,131 @@ const LoanTable: React.FC = () => {
|
||||
</VStack>
|
||||
)}
|
||||
{!isLoading && (
|
||||
<Table.Root size="sm" striped>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader>
|
||||
<strong>#</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Besitzer</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausleih code</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Startdatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Enddatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausleihdatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Rückgabedatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Erstellt am</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausgeliehene Artikel</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Notiz</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Aktionen</strong>
|
||||
</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{items.map((item) => (
|
||||
<Table.Row color={item.deleted ? "red" : "white"} key={item.id}>
|
||||
<Table.Cell>{item.id}</Table.Cell>
|
||||
<Table.Cell>{item.username}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Code>{item.loan_code}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.start_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.end_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.take_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.returned_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.created_at)}</Table.Cell>
|
||||
<Table.Cell>{item.loaned_items_name.join(", ")}</Table.Cell>
|
||||
<Table.Cell>{item.note}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
deleteLoan(item.id).then((response) => {
|
||||
if (response.success) {
|
||||
setItems(items.filter((i) => i.id !== item.id));
|
||||
setError(
|
||||
"success",
|
||||
"Loan deleted",
|
||||
"The loan has been successfully deleted."
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
colorPalette="red"
|
||||
size="sm"
|
||||
ml={2}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.ScrollArea flex="1" minH={0} rounded="md" mt={4}>
|
||||
<Table.Root
|
||||
size="sm"
|
||||
striped
|
||||
stickyHeader
|
||||
css={{
|
||||
"& [data-sticky]": {
|
||||
position: "sticky",
|
||||
zIndex: 1,
|
||||
bg: "bg",
|
||||
|
||||
_after: {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
top: "0",
|
||||
bottom: "-1px",
|
||||
width: "32px",
|
||||
},
|
||||
},
|
||||
|
||||
"& [data-sticky=end]": {
|
||||
_after: {
|
||||
insetInlineEnd: "0",
|
||||
translate: "100% 0",
|
||||
shadow: "inset 8px 0px 8px -8px rgba(0, 0, 0, 0.16)",
|
||||
},
|
||||
},
|
||||
|
||||
"& [data-sticky=start]": {
|
||||
_after: {
|
||||
insetInlineStart: "0",
|
||||
translate: "-100% 0",
|
||||
shadow: "inset -8px 0px 8px -8px rgba(0, 0, 0, 0.16)",
|
||||
},
|
||||
},
|
||||
|
||||
"& thead tr": {
|
||||
shadow: "0 1px 0 0 {colors.border}",
|
||||
"&:has(th[data-sticky])": {
|
||||
zIndex: 2,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader>
|
||||
<strong>#</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Besitzer</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausleihcode</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Startdatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Enddatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausleihdatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Rückgabedatum</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Erstellt am</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Ausgeliehene Artikel</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Notiz</strong>
|
||||
</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>
|
||||
<strong>Aktionen</strong>
|
||||
</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{items.map((item) => (
|
||||
<Table.Row color={item.deleted ? "red" : "white"} key={item.id}>
|
||||
<Table.Cell>{item.id}</Table.Cell>
|
||||
<Table.Cell>{item.username}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Code>{item.loan_code}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.start_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.end_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.take_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.returned_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(item.created_at)}</Table.Cell>
|
||||
<Table.Cell>{item.loaned_items_name.join(", ")}</Table.Cell>
|
||||
<Table.Cell>{item.note}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
deleteLoan(item.id).then((response) => {
|
||||
if (response.success) {
|
||||
setItems(items.filter((i) => i.id !== item.id));
|
||||
setError(
|
||||
"success",
|
||||
"Loan deleted",
|
||||
"The loan has been successfully deleted.",
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
colorPalette="red"
|
||||
size="sm"
|
||||
ml={2}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.ScrollArea>
|
||||
)}
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"backend-info": {
|
||||
"version": "v2.2 (dev)"
|
||||
"version": "v2.2 (demo)"
|
||||
},
|
||||
"frontend-info": {
|
||||
"version": "v2.2 (dev)"
|
||||
"version": "v2.2 (demo)"
|
||||
},
|
||||
"admin-panel-info": {
|
||||
"version": "v1.4(dev)"
|
||||
"version": "v1.4 (demo)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
USE borrow_system_new;
|
||||
|
||||
-- USERS
|
||||
INSERT INTO users (username, password, email, first_name, last_name, role, is_admin)
|
||||
VALUES
|
||||
('user1', 'passwordhash1', 'user1@example.com', 'First1', 'Last1', 1, false),
|
||||
('user2', 'passwordhash2', 'user2@example.com', 'First2', 'Last2', 1, false),
|
||||
('user3', 'passwordhash3', 'user3@example.com', 'First3', 'Last3', 2, false),
|
||||
('admin1', 'passwordhash4', 'admin1@example.com', 'Admin', 'One', 9, true),
|
||||
('admin2', 'passwordhash5', 'admin2@example.com', 'Admin', 'Two', 9, true);
|
||||
|
||||
-- ITEMS
|
||||
INSERT INTO items (item_name, can_borrow_role, in_safe, safe_nr, door_key, last_borrowed_person, currently_borrowing)
|
||||
VALUES
|
||||
('Item1', 1, true, 1, 101, NULL, NULL),
|
||||
('Item2', 1, true, 2, 102, 'user1', 'user1'),
|
||||
('Item3', 2, true, 3, 103, 'user2', NULL),
|
||||
('Item4', 1, false, NULL, NULL, NULL, NULL),
|
||||
('Item5', 2, false, NULL, NULL, 'user3', 'user3');
|
||||
|
||||
-- LOANS
|
||||
INSERT INTO loans (
|
||||
username,
|
||||
lockers,
|
||||
loan_code,
|
||||
start_date,
|
||||
end_date,
|
||||
take_date,
|
||||
returned_date,
|
||||
created_at,
|
||||
loaned_items_id,
|
||||
loaned_items_name,
|
||||
deleted,
|
||||
note
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'user1',
|
||||
JSON_ARRAY('Locker1', 'Locker2'),
|
||||
'123456',
|
||||
'2026-02-01 09:00:00',
|
||||
'2026-02-10 17:00:00',
|
||||
'2026-02-01 09:15:00',
|
||||
NULL,
|
||||
'2026-02-01 09:00:00',
|
||||
JSON_ARRAY(1, 2),
|
||||
JSON_ARRAY('Item1', 'Item2'),
|
||||
false,
|
||||
'Erste allgemeine Ausleihe'
|
||||
),
|
||||
(
|
||||
'user2',
|
||||
JSON_ARRAY('Locker3'),
|
||||
'234567',
|
||||
'2026-02-02 10:00:00',
|
||||
'2026-02-05 16:00:00',
|
||||
'2026-02-02 10:05:00',
|
||||
'2026-02-05 15:30:00',
|
||||
'2026-02-02 10:00:00',
|
||||
JSON_ARRAY(3),
|
||||
JSON_ARRAY('Item3'),
|
||||
false,
|
||||
'Zurückgegeben vor Enddatum'
|
||||
),
|
||||
(
|
||||
'user3',
|
||||
JSON_ARRAY(),
|
||||
'345678',
|
||||
'2026-02-03 08:30:00',
|
||||
'2026-02-15 18:00:00',
|
||||
NULL,
|
||||
NULL,
|
||||
'2026-02-03 08:30:00',
|
||||
JSON_ARRAY(5),
|
||||
JSON_ARRAY('Item5'),
|
||||
false,
|
||||
'Noch ausgeliehen'
|
||||
),
|
||||
(
|
||||
'user1',
|
||||
JSON_ARRAY('Locker4'),
|
||||
'456789',
|
||||
'2025-12-01 09:00:00',
|
||||
'2025-12-03 17:00:00',
|
||||
'2025-12-01 09:10:00',
|
||||
'2025-12-03 16:45:00',
|
||||
'2025-12-01 09:00:00',
|
||||
JSON_ARRAY(1),
|
||||
JSON_ARRAY('Item1'),
|
||||
true,
|
||||
'Alte, gelöschte Ausleihe'
|
||||
);
|
||||
|
||||
-- API KEYS
|
||||
INSERT INTO apiKeys (api_key, entry_name)
|
||||
VALUES
|
||||
('10000001', 'Entry1'),
|
||||
('10000002', 'Entry2'),
|
||||
('10000003', 'Entry3'),
|
||||
('10000004', 'Entry4');
|
||||
@@ -15,6 +15,7 @@ This update provides some new features for the design. It also contains some imp
|
||||
- Improved error logging
|
||||
- If you try to delete a loan that has not been returned yet, you will get an 507 error code.
|
||||
- When the admin deletes a loan, the loan will be still visible in the database, but it will be marked as deleted. This is to prevent data loss and to keep track of deleted loans.
|
||||
- Mailer improvements: The mailer is now more clearly organised. Two large code files are now split into five smaller code files which are easier to maintain. Also the design of the mails has improved.
|
||||
|
||||
## Fixed bugs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user