feat: Implement loan management features including fetching, creating, and deleting loans

- Added database functions for managing loans: getLoans, getUserLoans, deleteLoan, and createLoan.
- Updated frontend to include new Form4 for displaying user loans and handling loan deletions.
- Replaced Form3 with Form4 in App component.
- Enhanced Form1 to set borrowing dates and fetch available items.
- Improved Form2 to display borrowable items and allow selection for loans.
- Introduced utility functions for handling loan creation and deletion in userHandler.
- Added event listeners for local storage updates to keep UI in sync.
- Updated fetchData utility to retrieve loans and user loans from the backend.
This commit is contained in:
2025-08-19 19:13:52 +02:00
parent 1195e050da
commit 9287c949ca
12 changed files with 991 additions and 126 deletions

View File

@@ -3,11 +3,12 @@ import { myToast } from "./toastify";
// Event name used to notify the app when the list of items has been updated
export const ALL_ITEMS_UPDATED_EVENT = "allItemsUpdated";
export const BORROWABLE_ITEMS_UPDATED_EVENT = "borrowableItemsUpdated";
export const fetchAllData = async (token: string | undefined) => {
if (!token) return;
// First we fetch all items that are potentially available for borrowing
try {
// First we fetch all items that are potentially available for borrowing
const response = await fetch("http://localhost:8002/api/items", {
method: "GET",
headers: {
@@ -27,6 +28,50 @@ export const fetchAllData = async (token: string | undefined) => {
} catch (error) {
myToast("An error occurred", "error");
}
// get all loans
try {
const response = await fetch("http://localhost:8002/api/loans", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
myToast("Failed to fetch loans!", "error");
return;
}
const data = await response.json();
localStorage.setItem("allLoans", JSON.stringify(data));
// Notify listeners (e.g., Sidebar) that loans have been updated
window.dispatchEvent(new Event(ALL_ITEMS_UPDATED_EVENT));
} catch (error) {
myToast("An error occurred", "error");
}
// get user loans
try {
const response = await fetch("http://localhost:8002/api/userLoans", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
myToast("Failed to fetch user loans!", "error");
return;
}
const data = await response.json();
localStorage.setItem("userLoans", JSON.stringify(data));
// Notify listeners (e.g., Sidebar) that loans have been updated
window.dispatchEvent(new Event(ALL_ITEMS_UPDATED_EVENT));
} catch (error) {
myToast("An error occurred", "error");
}
};
export const loginUser = async (username: string, password: string) => {
@@ -56,3 +101,37 @@ export const loginUser = async (username: string, password: string) => {
return { success: false } as const;
}
};
export const getBorrowableItems = async () => {
const startDate = Cookies.get("startDate");
const endDate = Cookies.get("endDate");
if (!startDate || !endDate) {
myToast("Bitte wähle einen Zeitraum aus.", "error");
return;
}
try {
const response = await fetch("http://localhost:8002/api/borrowableItems", {
method: "POST",
headers: {
Authorization: `Bearer ${Cookies.get("token") || ""}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ startDate, endDate }),
});
if (!response.ok) {
myToast("Failed to fetch borrowable items", "error");
return;
}
const data = await response.json();
localStorage.setItem("borrowableItems", JSON.stringify(data));
window.dispatchEvent(new Event(BORROWABLE_ITEMS_UPDATED_EVENT)); // notify same-tab listeners
console.log("Borrowable items fetched successfully");
} catch (error) {
myToast("An error occurred", "error");
}
};

View File

@@ -0,0 +1,96 @@
import { myToast } from "./toastify";
import Cookies from "js-cookie";
export const handleDeleteLoan = async (loanID: number): Promise<boolean> => {
try {
const response = await fetch(
`http://localhost:8002/api/deleteLoan/${loanID}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${Cookies.get("token") || ""}`,
},
}
);
if (!response.ok) {
myToast("Fehler beim Löschen der Ausleihe", "error");
return false;
}
const raw = localStorage.getItem("userLoans");
let current: Array<{ id: number }> = [];
try {
const parsed = raw ? JSON.parse(raw) : [];
current = Array.isArray(parsed) ? parsed : [];
} catch {
current = [];
}
const updated = current.filter(
(loan) => Number(loan.id) !== Number(loanID)
);
localStorage.setItem("userLoans", JSON.stringify(updated));
myToast("Ausleihe erfolgreich gelöscht!", "success");
return true;
} catch (error) {
console.error("Error deleting loan:", error);
myToast("Fehler beim löschen der Ausleihe", "error");
return false;
}
};
// Parse existing cookie and coerce to numbers
let removeArr: number[] = (() => {
try {
const raw = Cookies.get("removeArr");
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed)
? parsed.map((v) => Number(v)).filter((n) => Number.isFinite(n))
: [];
} catch {
return [];
}
})();
const rawCookies = Cookies.withConverter({
write: (value: string) => value, // store raw JSON
});
export const addToRemove = (itemID: number) => {
if (!Number.isFinite(itemID)) return;
if (!removeArr.includes(itemID)) {
removeArr.push(itemID);
rawCookies.set("removeArr", JSON.stringify(removeArr));
}
};
export const rmFromRemove = (itemID: number) => {
removeArr = removeArr.filter((item) => item !== itemID);
rawCookies.set("removeArr", JSON.stringify(removeArr));
};
export const createLoan = async (startDate: string, endDate: string) => {
const items = removeArr;
const response = await fetch("http://localhost:8002/api/createLoan", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token") || ""}`,
},
body: JSON.stringify({ items, startDate, endDate }),
});
if (!response.ok) {
myToast("Fehler beim Erstellen der Ausleihe", "error");
return false;
}
// Clear selection on success
removeArr = [];
Cookies.set("removeArr", "[]");
myToast("Ausleihe erfolgreich erstellt!", "success");
return true;
};