From d05e9ab3ee895c4653ba927c2663b4fca6009364 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 9 Feb 2026 13:42:06 +0100 Subject: [PATCH 1/9] deleted unused changelog --- FrontendV2/src/components/Changelog.json | 50 ----- FrontendV2/src/components/Changelog.tsx | 263 ----------------------- 2 files changed, 313 deletions(-) delete mode 100644 FrontendV2/src/components/Changelog.json delete mode 100644 FrontendV2/src/components/Changelog.tsx diff --git a/FrontendV2/src/components/Changelog.json b/FrontendV2/src/components/Changelog.json deleted file mode 100644 index df87241..0000000 --- a/FrontendV2/src/components/Changelog.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "title": "Changelog", - "items": [ - { - "version": "v2.1.0", - "date": "2025-10-24", - "changes": [ - { - "type": "Hinzugefügt", - "text": [ - "Neue Changelog-Komponente mit zentriertem Layout.", - "Unterstützung für mehrsprachige Einträge (Englisch und Deutsch)." - ] - }, - { - "type": "Verbessert", - "text": [ - "Performance-Optimierungen beim Laden der Listenansichten.", - "Verbesserte Barrierefreiheit durch ARIA-Attribute." - ] - }, - { - "type": "Behoben", - "text": [ - "Fehler bei der Datumsauswahl im Safari-Browser.", - "Anzeigeprobleme bei hohen DPI-Einstellungen." - ] - } - ] - }, - { - "version": "v2.0.3", - "date": "2025-10-10", - "changes": [ - { - "type": "Geändert", - "text": [ - "Standard-Timeout für API-Requests auf 10s erhöht." - ] - }, - { - "type": "Sicherheit", - "text": [ - "Abhängigkeiten aktualisiert (kritische CVEs behoben)." - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/FrontendV2/src/components/Changelog.tsx b/FrontendV2/src/components/Changelog.tsx deleted file mode 100644 index 7ca3095..0000000 --- a/FrontendV2/src/components/Changelog.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -const STORAGE_KEY = "changelog"; - -type ChangeType = - | "Hinzugefügt" - | "Geändert" - | "Behoben" - | "Entfernt" - | "Verbessert" - | "Sicherheit" - | "Veraltet" - | string; - -type ChangeEntry = { - type: ChangeType; - text: string | string[]; // aus localStorage kann es eine Liste sein -}; - -type ChangelogItem = { - version?: string; - date: string; - changes: ChangeEntry[]; -}; - -type StoredChangelog = { - title: string; - items: ChangelogItem[]; -}; - -const typeStyles: Record = { - Hinzugefügt: - "bg-emerald-500/15 text-emerald-300 ring-1 ring-inset ring-emerald-500/30", - Geändert: "bg-blue-500/15 text-blue-300 ring-1 ring-inset ring-blue-500/30", - Behoben: "bg-amber-500/15 text-amber-300 ring-1 ring-inset ring-amber-500/30", - Entfernt: "bg-rose-500/15 text-rose-300 ring-1 ring-inset ring-rose-500/30", - Verbessert: - "bg-indigo-500/15 text-indigo-300 ring-1 ring-inset ring-indigo-500/30", - Sicherheit: "bg-red-500/15 text-red-300 ring-1 ring-inset ring-red-500/30", - Veraltet: "bg-zinc-700/30 text-zinc-300 ring-1 ring-inset ring-zinc-600/40", -}; - -export default function Changelog() { - const [open, setOpen] = useState(true); - const [mounted, setMounted] = useState(false); - const [data, setData] = useState(null); - const [error, setError] = useState(null); - const cardRef = useRef(null); - - useEffect(() => setMounted(true), []); - - const loadFromStorage = () => { - try { - setError(null); - const raw = - typeof window !== "undefined" - ? localStorage.getItem(STORAGE_KEY) - : null; - if (!raw) { - setData(null); - return; - } - const parsed = JSON.parse(raw) as StoredChangelog; - if (!parsed || !Array.isArray(parsed.items)) { - throw new Error("Ungültiges Format"); - } - setData(parsed); - } catch (e) { - setError("Changelog konnte nicht aus localStorage geladen werden."); - setData(null); - } - }; - - useEffect(() => { - loadFromStorage(); - }, []); - - useEffect(() => { - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") setOpen(false); - }; - const onClickOutside = (e: MouseEvent) => { - if (cardRef.current && !cardRef.current.contains(e.target as Node)) { - setOpen(false); - } - }; - const onStorage = (e: StorageEvent) => { - if (e.key === STORAGE_KEY) loadFromStorage(); - }; - window.addEventListener("keydown", onKey); - document.addEventListener("mousedown", onClickOutside); - window.addEventListener("storage", onStorage); - return () => { - window.removeEventListener("keydown", onKey); - document.removeEventListener("mousedown", onClickOutside); - window.removeEventListener("storage", onStorage); - }; - }, []); - - if (!open) return null; - - const title = data?.title ?? "Changelog"; - const items = data?.items ?? []; - - return ( -
-
- {/* Gradient border wrapper */} -
- {/* Card */} -
- {/* Accent top line */} -
- - {/* Close button */} - - - {/* Header */} -
-
-
- - - -
-
-

- {title} -

-

- Aktuelle Änderungen und Updates -

-
-
-
- - {/* Body */} -
-
-
- - {error && ( -
-
- {error} -
-
- )} - - {!error && items.length === 0 && ( -
-

- Kein Changelog im localStorage gefunden (Key: {STORAGE_KEY} - ). -

-
- )} - -
    - {items.map((entry, idx) => ( -
  • - {/* Kopfzeile je Release */} -
    - {entry.version && ( - - {entry.version} - - )} - -
    - - {/* Zweispaltiges Layout: Typ links, Text rechts (mit schöner Leselänge) */} -
    - {entry.changes.map((c, i) => ( -
    -
    - - {c.type} - -
    - -
    - {Array.isArray(c.text) ? ( -
      - {c.text.map((t, k) => ( -
    • - {t} -
    • - ))} -
    - ) : ( -

    {c.text}

    - )} -
    -
    - ))} -
    -
  • - ))} -
-
- - {/* soft bottom glow */} -
-
-
-
-
- ); -} From 757b316b495751ee2982070fe28fa6d65f9f48b2 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 9 Feb 2026 13:42:19 +0100 Subject: [PATCH 2/9] fixed translation bug --- FrontendV2/src/utils/Fetcher.ts | 10 ++++++---- FrontendV2/src/utils/i18n/locales/de/de.json | 1 + FrontendV2/src/utils/i18n/locales/en/en.json | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/FrontendV2/src/utils/Fetcher.ts b/FrontendV2/src/utils/Fetcher.ts index 035de1f..c6be318 100644 --- a/FrontendV2/src/utils/Fetcher.ts +++ b/FrontendV2/src/utils/Fetcher.ts @@ -1,10 +1,13 @@ import Cookies from "js-cookie"; import { API_BASE } from "@/config/api.config"; +import { useTranslation } from "react-i18next"; export const getBorrowableItems = async ( startDate: string, - endDate: string + endDate: string, ) => { + const { t } = useTranslation(); + try { const response = await fetch(`${API_BASE}/api/loans/borrowable-items`, { method: "POST", @@ -21,8 +24,7 @@ export const getBorrowableItems = async ( data: null, status: "error", title: "Server error", - description: - "Ein Fehler ist auf dem Server aufgetreten. Manchmal hilft es, die Seite neu zu laden.", + description: t("serverError"), }; } @@ -48,7 +50,7 @@ export const createLoan = async ( itemIds: number[], startDate: string, endDate: string, - note: string | null + note: string | null, ) => { const response = await fetch(`${API_BASE}/api/loans/createLoan`, { method: "POST", diff --git a/FrontendV2/src/utils/i18n/locales/de/de.json b/FrontendV2/src/utils/i18n/locales/de/de.json index 5d39c80..f6ed2dc 100644 --- a/FrontendV2/src/utils/i18n/locales/de/de.json +++ b/FrontendV2/src/utils/i18n/locales/de/de.json @@ -84,6 +84,7 @@ "contact": "Kontakt", "take": "Abholen", "return": "Zurückgeben", + "serverError": "Serverfehler. Bitte versuchen Sie es später erneut, oder laden Sie die Seite neu.", "take-loan-success": "Ausleihe erfolgreich abgeholt", "return-loan-success": "Ausleihe erfolgreich zurückgegeben", "network-error": "Netzwerkfehler. Kontaktieren Sie den Administrator.", diff --git a/FrontendV2/src/utils/i18n/locales/en/en.json b/FrontendV2/src/utils/i18n/locales/en/en.json index 13dec04..6a52916 100644 --- a/FrontendV2/src/utils/i18n/locales/en/en.json +++ b/FrontendV2/src/utils/i18n/locales/en/en.json @@ -82,6 +82,7 @@ "contactPage_messagePlaceholder": "Enter your message here...", "contactPage_messageErrorText": "This field cannot be empty.", "contact": "Contact", + "serverError": "Server error. Please try again later, or refresh the page.", "take": "Take", "return": "Return", "take-loan-success": "Loan taken successfully", From 38c647c62f3623119e4252d3ec05c1213d3bca79 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 9 Feb 2026 15:49:51 +0100 Subject: [PATCH 3/9] Fixed bug/issue: #13 --- FrontendV2/src/components/footer/Footer.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/FrontendV2/src/components/footer/Footer.tsx b/FrontendV2/src/components/footer/Footer.tsx index 3d11c18..4732e17 100644 --- a/FrontendV2/src/components/footer/Footer.tsx +++ b/FrontendV2/src/components/footer/Footer.tsx @@ -5,15 +5,7 @@ export const Footer = () => { const { data: info } = useVersionInfoQuery(); return ( - + Made with ❤️ by Theis Gaedigk - Class of 2019 at MCS-Bochum
Frontend-Version: {info ? info["frontend-info"].version : "N/A"} | From 977a6c1b16e9e21506e5a2f186d00a603a9d181c Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 9 Feb 2026 15:51:00 +0100 Subject: [PATCH 4/9] deltedt mock data because its too old --- backendV2/schemeV2.mock_data.sql | 120 ------------------------------- 1 file changed, 120 deletions(-) delete mode 100644 backendV2/schemeV2.mock_data.sql diff --git a/backendV2/schemeV2.mock_data.sql b/backendV2/schemeV2.mock_data.sql deleted file mode 100644 index 7201901..0000000 --- a/backendV2/schemeV2.mock_data.sql +++ /dev/null @@ -1,120 +0,0 @@ -USE borrow_system_new; - --- Reset tables (no FKs defined, so order is safe) -SET FOREIGN_KEY_CHECKS = 0; -TRUNCATE TABLE loans; -TRUNCATE TABLE apiKeys; -TRUNCATE TABLE items; -TRUNCATE TABLE users; -SET FOREIGN_KEY_CHECKS = 1; - --- Users (roles 1–6, plain-text passwords; is_admin is BOOL) -INSERT INTO users (username, password, email, first_name, last_name, role, is_admin) VALUES -('admin', 'adminpass', 'admin@example.com', 'System', 'Admin', 6, TRUE), -('alice', 'alice123', 'alice@example.com', 'Alice', 'Andersen',1, FALSE), -('bob', 'bob12345', 'bob@example.com', 'Bob', 'Berg', 2, FALSE), -('carol', 'carol123', 'carol@example.com', 'Carol', 'Christensen', 3, FALSE), -('dave', 'dave123', 'dave@example.com', 'Dave', 'Dahl', 4, FALSE), -('erin', 'erin123', 'erin@example.com', 'Erin', 'Enevoldsen', 5, FALSE), -('frank', 'frank123', 'frank@example.com', 'Frank', 'Fisher', 2, FALSE), -('grace', 'grace123', 'grace@example.com', 'Grace', 'Gundersen',1, FALSE), -('heidi', 'heidi123', 'heidi@example.com', 'Heidi', 'Hansen', 4, FALSE), -('tech', 'techpass', 'tech@example.com', 'Tech', 'User', 5, TRUE); - --- Items (safe_nr is two digits or NULL; matches CHECK and UNIQUE constraint) -INSERT INTO items (item_name, can_borrow_role, in_safe, safe_nr, last_borrowed_person, currently_borrowing) VALUES -('Laptop A', 2, FALSE, NULL, 'grace', 'bob'), -('Laptop B', 2, TRUE, '01', NULL, NULL), -('Camera Canon', 3, TRUE, '02', 'erin', NULL), -('Microphone Rode', 1, TRUE, '03', 'grace', NULL), -('Tripod Manfrotto', 1, TRUE, '04', 'frank', NULL), -('Oscilloscope Tek', 4, TRUE, '05', NULL, NULL), -('VR Headset', 3, FALSE, NULL, 'heidi', 'carol'), -('Keycard Programmer', 6, TRUE, '06', 'admin', NULL); - --- Loans (JSON strings, 6-digit numeric loan_code per CHECK) --- Assumes the items above have ids 1..8 in insert order -INSERT INTO loans ( - username, - lockers, - loan_code, - start_date, - end_date, - take_date, - returned_date, - loaned_items_id, - loaned_items_name, - deleted, - note -) VALUES --- Active loan: bob has Laptop A (item id 1, locker "01") -('bob', - '["01"]', - '123456', - '2025-11-15 09:00:00', - '2025-11-22 17:00:00', - '2025-11-15 09:15:00', - NULL, - '[1]', - '["Laptop A"]', - FALSE, - 'Active loan - Laptop A' -), --- Returned loan: frank had Tripod Manfrotto (item id 5, locker "04") -('frank', - '["04"]', - '234567', - '2025-10-01 10:00:00', - '2025-10-07 16:00:00', - '2025-10-01 10:05:00', - '2025-10-05 15:30:00', - '[5]', - '["Tripod Manfrotto"]', - FALSE, - 'Completed loan' -), --- Future reservation: dave will take Oscilloscope Tek (item id 6, locker "05") -('dave', - '["05"]', - '345678', - '2025-12-10 09:00:00', - '2025-12-12 17:00:00', - NULL, - NULL, - '[6]', - '["Oscilloscope Tek"]', - FALSE, - 'Reserved' -), --- Active loan: carol has VR Headset (item id 7, locker "02") -('carol', - '["02"]', - '456789', - '2025-11-10 13:00:00', - '2025-11-20 12:00:00', - '2025-11-10 13:10:00', - NULL, - '[7]', - '["VR Headset"]', - FALSE, - 'Active loan - VR Headset' -), --- Soft-deleted historic loan: grace had Microphone + Tripod (item ids 4,5; lockers "03","04") -('grace', - '["03","04"]', - '567890', - '2025-09-01 09:00:00', - '2025-09-03 17:00:00', - '2025-09-01 09:10:00', - '2025-09-03 16:45:00', - '[4,5]', - '["Microphone Rode","Tripod Manfrotto"]', - TRUE, - 'Canceled/soft-deleted record' -); - --- API keys (8-digit numeric keys per CHECK) -INSERT INTO apiKeys (api_key, entry_name, last_used_at) VALUES -('12345678', 'CI token', '2025-11-15 08:00:00'), -('87654321', 'Local dev', NULL), -('00000001', 'Monitoring', '2025-11-10 12:30:00'); \ No newline at end of file From 06976f7972d7333fe8b3089d3dff9cd09d6f5f86 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Sat, 14 Feb 2026 19:01:59 +0100 Subject: [PATCH 5/9] added pasword input to admin panel --- admin/src/Layout/Login.tsx | 4 +- admin/src/components/ui/password-input.tsx | 159 +++++++++++++++++++++ admin/tsconfig.app.json | 10 +- 3 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 admin/src/components/ui/password-input.tsx diff --git a/admin/src/Layout/Login.tsx b/admin/src/Layout/Login.tsx index 813a7e6..590826c 100644 --- a/admin/src/Layout/Login.tsx +++ b/admin/src/Layout/Login.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { loginFunc } from "@/utils/loginUser"; import MyAlert from "../components/myChakra/MyAlert"; import { Button, Card, Field, Input, Stack } from "@chakra-ui/react"; +import { PasswordInput } from "@/components/ui/password-input"; const Login: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => { const [username, setUsername] = useState(""); @@ -43,8 +44,7 @@ const Login: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => { password - setPassword(e.target.value)} /> diff --git a/admin/src/components/ui/password-input.tsx b/admin/src/components/ui/password-input.tsx new file mode 100644 index 0000000..3b9af80 --- /dev/null +++ b/admin/src/components/ui/password-input.tsx @@ -0,0 +1,159 @@ +"use client" + +import type { + ButtonProps, + GroupProps, + InputProps, + StackProps, +} from "@chakra-ui/react" +import { + Box, + HStack, + IconButton, + Input, + InputGroup, + Stack, + mergeRefs, + useControllableState, +} from "@chakra-ui/react" +import * as React from "react" +import { LuEye, LuEyeOff } from "react-icons/lu" + +export interface PasswordVisibilityProps { + /** + * The default visibility state of the password input. + */ + defaultVisible?: boolean + /** + * The controlled visibility state of the password input. + */ + visible?: boolean + /** + * Callback invoked when the visibility state changes. + */ + onVisibleChange?: (visible: boolean) => void + /** + * Custom icons for the visibility toggle button. + */ + visibilityIcon?: { on: React.ReactNode; off: React.ReactNode } +} + +export interface PasswordInputProps + extends InputProps, + PasswordVisibilityProps { + rootProps?: GroupProps +} + +export const PasswordInput = React.forwardRef< + HTMLInputElement, + PasswordInputProps +>(function PasswordInput(props, ref) { + const { + rootProps, + defaultVisible, + visible: visibleProp, + onVisibleChange, + visibilityIcon = { on: , off: }, + ...rest + } = props + + const [visible, setVisible] = useControllableState({ + value: visibleProp, + defaultValue: defaultVisible || false, + onChange: onVisibleChange, + }) + + const inputRef = React.useRef(null) + + return ( + { + if (rest.disabled) return + if (e.button !== 0) return + e.preventDefault() + setVisible(!visible) + }} + > + {visible ? visibilityIcon.off : visibilityIcon.on} + + } + {...rootProps} + > + + + ) +}) + +const VisibilityTrigger = React.forwardRef( + function VisibilityTrigger(props, ref) { + return ( + + ) + }, +) + +interface PasswordStrengthMeterProps extends StackProps { + max?: number + value: number +} + +export const PasswordStrengthMeter = React.forwardRef< + HTMLDivElement, + PasswordStrengthMeterProps +>(function PasswordStrengthMeter(props, ref) { + const { max = 4, value, ...rest } = props + + const percent = (value / max) * 100 + const { label, colorPalette } = getColorPalette(percent) + + return ( + + + {Array.from({ length: max }).map((_, index) => ( + + ))} + + {label && {label}} + + ) +}) + +function getColorPalette(percent: number) { + switch (true) { + case percent < 33: + return { label: "Low", colorPalette: "red" } + case percent < 66: + return { label: "Medium", colorPalette: "orange" } + default: + return { label: "High", colorPalette: "green" } + } +} diff --git a/admin/tsconfig.app.json b/admin/tsconfig.app.json index 8ad072c..a824e0e 100644 --- a/admin/tsconfig.app.json +++ b/admin/tsconfig.app.json @@ -1,10 +1,11 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ESNext", + "target": "ES2022", "useDefineForClassFields": true, "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", + "types": ["vite/client"], "skipLibCheck": true, /* Bundler mode */ @@ -23,13 +24,10 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - /* Chakra / Pfad Aliases */ - "baseUrl": ".", + /* Path aliases */ "paths": { "@/*": ["./src/*"] - }, - - "forceConsistentCasingInFileNames": true + } }, "include": ["src"] } From a8dab549af06ee38082c7519013f197f14e2fe21 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Fri, 20 Feb 2026 12:02:33 +0100 Subject: [PATCH 6/9] fixed bug: cannot return loan --- FrontendV2/src/utils/Fetcher.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/FrontendV2/src/utils/Fetcher.ts b/FrontendV2/src/utils/Fetcher.ts index c6be318..a684490 100644 --- a/FrontendV2/src/utils/Fetcher.ts +++ b/FrontendV2/src/utils/Fetcher.ts @@ -1,13 +1,10 @@ import Cookies from "js-cookie"; import { API_BASE } from "@/config/api.config"; -import { useTranslation } from "react-i18next"; export const getBorrowableItems = async ( startDate: string, endDate: string, ) => { - const { t } = useTranslation(); - try { const response = await fetch(`${API_BASE}/api/loans/borrowable-items`, { method: "POST", @@ -24,7 +21,8 @@ export const getBorrowableItems = async ( data: null, status: "error", title: "Server error", - description: t("serverError"), + description: + "An error occurred on the server. Sometimes reloading the page helps. Otherwise, please contact the administrator.", }; } From ee54d51f8bac0c4aa0d69efeb12440a5624a3c26 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Fri, 20 Feb 2026 12:14:56 +0100 Subject: [PATCH 7/9] enhanced loan management: added note field to loan creation and email templates --- backendV2/routes/api/api.database.js | 63 +++++++++++++------------ backendV2/routes/app/loanMgmt.route.js | 1 + backendV2/routes/app/services/mailer.js | 44 +++++++++++++++-- 3 files changed, 73 insertions(+), 35 deletions(-) diff --git a/backendV2/routes/api/api.database.js b/backendV2/routes/api/api.database.js index d80365c..b824ad4 100644 --- a/backendV2/routes/api/api.database.js +++ b/backendV2/routes/api/api.database.js @@ -22,7 +22,7 @@ export const getItemsFromDatabaseV2 = async () => { export const getLoanByCodeV2 = async (loan_code) => { const [result] = await pool.query( "SELECT username, returned_date, take_date, lockers FROM loans WHERE loan_code = ?;", - [loan_code] + [loan_code], ); if (result.length > 0) { return { success: true, data: result[0] }; @@ -33,7 +33,7 @@ export const getLoanByCodeV2 = async (loan_code) => { export const changeInSafeStateV2 = async (itemId) => { const [result] = await pool.query( "UPDATE items SET in_safe = NOT in_safe WHERE id = ?", - [itemId] + [itemId], ); if (result.affectedRows > 0) { return { success: true }; @@ -42,47 +42,48 @@ export const changeInSafeStateV2 = async (itemId) => { }; export const setReturnDateV2 = async (loanCode) => { - const [items] = await pool.query( - "SELECT loaned_items_id FROM loans WHERE loan_code = ?", - [loanCode] - ); + try { + const [items] = await pool.query( + "SELECT loaned_items_id, username FROM loans WHERE loan_code = ?", + [loanCode], + ); - const [owner] = await pool.query( - "SELECT username FROM loans WHERE loan_code = ?", - [loanCode] - ); + if (items.length === 0) return { success: false }; - if (items.length === 0) return { success: false }; + const itemIds = Array.isArray(items[0].loaned_items_id) + ? items[0].loaned_items_id + : JSON.parse(items[0].loaned_items_id || "[]"); - const itemIds = Array.isArray(items[0].loaned_items_id) - ? items[0].loaned_items_id - : JSON.parse(items[0].loaned_items_id || "[]"); + const [result] = await pool.query( + "UPDATE loans SET returned_date = NOW() WHERE loan_code = ? AND returned_date IS NULL", + [loanCode], + ); - const [setItemStates] = await pool.query( - "UPDATE items SET in_safe = 1, currently_borrowing = NULL, last_borrowed_person = (?) WHERE id IN (?)", - [owner[0].username, itemIds] - ); + if (result.affectedRows === 0) return { success: false }; - const [result] = await pool.query( - "UPDATE loans SET returned_date = NOW() WHERE loan_code = ?", - [loanCode] - ); + if (itemIds.length > 0) { + await pool.query( + "UPDATE items SET in_safe = 1, currently_borrowing = NULL, last_borrowed_person = ? WHERE id IN (?)", + [items[0].username, itemIds], + ); + } - if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { - return { success: true }; + return { success: true, data: { returned: true } }; + } catch (error) { + console.error("setReturnDateV2 error:", error); + return { success: false }; } - return { success: false }; }; export const setTakeDateV2 = async (loanCode) => { const [items] = await pool.query( "SELECT loaned_items_id FROM loans WHERE loan_code = ?", - [loanCode] + [loanCode], ); const [owner] = await pool.query( "SELECT username FROM loans WHERE loan_code = ?", - [loanCode] + [loanCode], ); if (items.length === 0) return { success: false }; @@ -93,12 +94,12 @@ export const setTakeDateV2 = async (loanCode) => { const [setItemStates] = await pool.query( "UPDATE items SET in_safe = 0, currently_borrowing = (?) WHERE id IN (?)", - [owner[0].username, itemIds] + [owner[0].username, itemIds], ); const [result] = await pool.query( "UPDATE loans SET take_date = NOW() WHERE loan_code = ?", - [loanCode] + [loanCode], ); if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { @@ -118,12 +119,12 @@ export const getAllLoansV2 = async () => { export const openDoor = async (doorKey) => { const [result] = await pool.query( "SELECT safe_nr, id FROM items WHERE door_key = ?;", - [doorKey] + [doorKey], ); if (result.length > 0) { const [changeItemSate] = await pool.query( "UPDATE items SET in_safe = NOT in_safe WHERE id = ?", - [result[0].id] + [result[0].id], ); if (changeItemSate.affectedRows > 0) { return { success: true, data: result[0] }; diff --git a/backendV2/routes/app/loanMgmt.route.js b/backendV2/routes/app/loanMgmt.route.js index 3ec5599..7e0c0ce 100644 --- a/backendV2/routes/app/loanMgmt.route.js +++ b/backendV2/routes/app/loanMgmt.route.js @@ -62,6 +62,7 @@ router.post("/createLoan", authenticate, async (req, res) => { mailInfo.data.start_date, mailInfo.data.end_date, mailInfo.data.created_at, + mailInfo.data.note, ); return res.status(201).json({ message: "Loan created successfully", diff --git a/backendV2/routes/app/services/mailer.js b/backendV2/routes/app/services/mailer.js index 1357d31..3a081d6 100644 --- a/backendV2/routes/app/services/mailer.js +++ b/backendV2/routes/app/services/mailer.js @@ -34,7 +34,14 @@ const formatDateTime = (value) => { return "N/A"; }; -function buildLoanEmail({ user, items, startDate, endDate, createdDate }) { +function buildLoanEmail({ + user, + items, + startDate, + endDate, + createdDate, + note, +}) { const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9"; const itemsList = Array.isArray(items) && items.length @@ -116,6 +123,12 @@ function buildLoanEmail({ user, items, startDate, endDate, createdDate }) { createdDate, )} + + Notiz + ${ + note || "Keine Notiz" + } +

@@ -134,7 +147,14 @@ function buildLoanEmail({ user, items, startDate, endDate, createdDate }) { `; } -function buildLoanEmailText({ user, items, startDate, endDate, createdDate }) { +function buildLoanEmailText({ + user, + items, + startDate, + endDate, + createdDate, + note, +}) { const itemsText = Array.isArray(items) && items.length ? items.join(", ") : "N/A"; return [ @@ -145,10 +165,18 @@ function buildLoanEmailText({ user, items, startDate, endDate, createdDate }) { `Start: ${formatDateTime(startDate)}`, `Ende: ${formatDateTime(endDate)}`, `Erstellt am: ${formatDateTime(createdDate)}`, + `Notiz: ${note || "Keine Notiz"}`, ].join("\n"); } -export function sendMailLoan(user, items, startDate, endDate, createdDate) { +export function sendMailLoan( + user, + items, + startDate, + endDate, + createdDate, + note, +) { const transporter = nodemailer.createTransport({ host: process.env.MAIL_HOST, port: process.env.MAIL_PORT, @@ -170,8 +198,16 @@ export function sendMailLoan(user, items, startDate, endDate, createdDate) { startDate, endDate, createdDate, + note, + }), + html: buildLoanEmail({ + user, + items, + startDate, + endDate, + createdDate, + note, }), - html: buildLoanEmail({ user, items, startDate, endDate, createdDate }), }); console.log("Loan message sent:", info.messageId); From 3ba3c1c0cb2999550d47ef385779f0f1f9d4080a Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Fri, 20 Feb 2026 16:22:13 +0100 Subject: [PATCH 8/9] improved error logging for the api route to return or take loans --- backendV2/routes/api/api.database.js | 21 ++++++++++++++++----- backendV2/routes/api/api.route.js | 12 ++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/backendV2/routes/api/api.database.js b/backendV2/routes/api/api.database.js index b824ad4..d0a13d3 100644 --- a/backendV2/routes/api/api.database.js +++ b/backendV2/routes/api/api.database.js @@ -48,7 +48,8 @@ export const setReturnDateV2 = async (loanCode) => { [loanCode], ); - if (items.length === 0) return { success: false }; + if (items.length === 0) + return { success: false, message: "No items found for loan" }; const itemIds = Array.isArray(items[0].loaned_items_id) ? items[0].loaned_items_id @@ -71,11 +72,20 @@ export const setReturnDateV2 = async (loanCode) => { return { success: true, data: { returned: true } }; } catch (error) { console.error("setReturnDateV2 error:", error); - return { success: false }; + return { success: false, message: "Failed to set return date" }; } }; export const setTakeDateV2 = async (loanCode) => { + const [isTaken] = await pool.query( + "SELECT take_date FROM loans WHERE loan_code = ?", + [loanCode], + ); + + if (isTaken.length === 0 || isTaken[0].take_date !== null) { + return { success: false, message: "Loan not found or already taken" }; + } + const [items] = await pool.query( "SELECT loaned_items_id FROM loans WHERE loan_code = ?", [loanCode], @@ -86,7 +96,8 @@ export const setTakeDateV2 = async (loanCode) => { [loanCode], ); - if (items.length === 0) return { success: false }; + if (items.length === 0) + return { success: false, message: "No items found for loan" }; const itemIds = Array.isArray(items[0].loaned_items_id) ? items[0].loaned_items_id @@ -98,14 +109,14 @@ export const setTakeDateV2 = async (loanCode) => { ); const [result] = await pool.query( - "UPDATE loans SET take_date = NOW() WHERE loan_code = ?", + "UPDATE loans SET take_date = NOW() WHERE loan_code = ? AND take_date IS NULL", [loanCode], ); if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { return { success: true }; } - return { success: false }; + return { message: "Failed to set take date", success: false }; }; export const getAllLoansV2 = async () => { diff --git a/backendV2/routes/api/api.route.js b/backendV2/routes/api/api.route.js index 56a289d..fc638d8 100644 --- a/backendV2/routes/api/api.route.js +++ b/backendV2/routes/api/api.route.js @@ -47,7 +47,7 @@ router.get( } else { res.status(404).json({ message: "Loan not found" }); } - } + }, ); // Route for API to set the return date by the loan code @@ -58,11 +58,11 @@ router.post( const loanCode = req.params.loan_code; const result = await setReturnDateV2(loanCode); if (result.success) { - res.status(200).json({ data: result.data }); + res.status(200).json({}); } else { res.status(500).json({ message: "Failed to set return date" }); } - } + }, ); // Route for API to set the take away date by the loan code @@ -73,11 +73,11 @@ router.post( const loanCode = req.params.loan_code; const result = await setTakeDateV2(loanCode); if (result.success) { - res.status(200).json({ data: result.data }); + res.status(200).json({}); } else { - res.status(500).json({ message: "Failed to set take date" }); + res.status(500).json({ message: result.message }); } - } + }, ); // Route for API to open a door From 1fa8b4a9a722b2c0b985def0c6ca1aee808985f3 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Fri, 20 Feb 2026 16:30:20 +0100 Subject: [PATCH 9/9] refactor: clean up layout components and improve footer styling --- FrontendV2/src/App.tsx | 8 ++++---- FrontendV2/src/components/footer/Footer.tsx | 9 ++++++++- FrontendV2/src/pages/LoginPage.tsx | 4 +--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/FrontendV2/src/App.tsx b/FrontendV2/src/App.tsx index 400fe4f..58d81eb 100644 --- a/FrontendV2/src/App.tsx +++ b/FrontendV2/src/App.tsx @@ -12,7 +12,7 @@ import { triggerLogoutAtom } from "@/states/Atoms"; import { MyLoansPage } from "./pages/MyLoansPage"; import Landingpage from "./pages/Landingpage"; import { changeLanguage } from "i18next"; -import { Box, Flex } from "@chakra-ui/react"; +import { Flex } from "@chakra-ui/react"; import { Footer } from "./components/footer/Footer"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { API_BASE } from "@/config/api.config"; @@ -72,8 +72,8 @@ function App() { return ( - - + + @@ -88,7 +88,7 @@ function App() { - +