From b534957c971108f75ee3c3bb1b00c457ad2f15ac Mon Sep 17 00:00:00 2001 From: jpie02 <128189069+jpie02@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:33:35 +0100 Subject: [PATCH 1/5] Input bar active outline fixes. --- web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx | 6 +++--- web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx b/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx index ccfd4905..2f54f157 100644 --- a/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx +++ b/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx @@ -85,21 +85,21 @@ const LibraryAdminHomePage: React.FC = () => { placeholder="Nazwa użytkownika" value={newLibrarian.username} onChange={(e) => setNewLibrarian({...newLibrarian, username: e.target.value})} - className="border border-gray-300 rounded p-2" + className="w-full p-2 rounded-lg border-2 outline-none bg-white text-[#3b4248] focus:outline-none focus:ring-2 focus:ring-[#3B576C]" /> setNewLibrarian({...newLibrarian, firstName: e.target.value})} - className="border border-gray-300 rounded p-2" + className="w-full p-2 rounded-lg border-2 outline-none bg-white text-[#3b4248] focus:outline-none focus:ring-2 focus:ring-[#3B576C]" /> setNewLibrarian({...newLibrarian, lastName: e.target.value})} - className="border border-gray-300 rounded p-2" + className="w-full p-2 rounded-lg border-2 outline-none bg-white text-[#3b4248] focus:outline-none focus:ring-2 focus:ring-[#3B576C]" />
diff --git a/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx b/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx index d4db28b2..f6bbaa7d 100644 --- a/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx +++ b/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx @@ -160,7 +160,7 @@ const LibraryAdminHomePage: React.FC = () => { placeholder="Nazwa użytkownika" value={usernameSearch} onChange={(e) => setUsernameSearch(e.target.value)} - className="border border-gray-300 rounded p-2 w-full pr-10" + className="w-full p-2 rounded-lg border-2 outline-none bg-white text-[#3b4248] focus:outline-none focus:ring-2 focus:ring-[#3B576C]" /> {usernameSearch && (
)} - {message &&

{message}

} + {message &&

{message}

} From b448a79e89092f706b9f9351b573530d8e7424f9 Mon Sep 17 00:00:00 2001 From: jpie02 <128189069+jpie02@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:23:23 +0100 Subject: [PATCH 3/5] Fix the delete librarian functionality of LibraryAdminHomePage.tsx to display an alert window for confirmation. Add additional username information in success messages both LibraryAdminHomePage.tsx ans LibraryAdminAddLibrarian.tsx --- web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx | 2 +- web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx b/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx index 2f54f157..1beb5da4 100644 --- a/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx +++ b/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx @@ -21,7 +21,7 @@ const LibraryAdminHomePage: React.FC = () => { body: JSON.stringify(newLibrarian), }); if (res.ok) { - setMessage('Dodano bibliotekarza.'); + setMessage('Dodano bibliotekarza o nazwie użytkownika ' + newLibrarian.username + '.' ); setNewLibrarian({ username: '', firstName: '', lastName: '' }); } else { setMessage('Nie udało się dodać bibliotekarza.'); diff --git a/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx b/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx index 655082de..cd02983a 100644 --- a/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx +++ b/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx @@ -108,6 +108,10 @@ const LibraryAdminHomePage: React.FC = () => { }; const deleteLibrarian = async (username: string) => { + if (!window.confirm(`Czy na pewno chcesz konto użytkownika "${username}"? Tej operacji nie można cofnąć.`)) { + return; + } + const token = localStorage.getItem('access_token'); try { const res = await fetch(`${API_BASE_URL}/api/library-admins/librarians/${username}`, { @@ -118,7 +122,7 @@ const LibraryAdminHomePage: React.FC = () => { }); if (res.ok) { setLibrarians(librarians.filter(lib => lib.username !== username)); - setMessage('Usunięto bibliotekarza.'); + setMessage('Usunięto bibliotekarza o nazwie użytkownika ' + username + '.'); } else { setMessage('Nie udało się usunąć bibliotekarza.'); } From 9a459a10bf9ed79827f353def1e522baac7a74dd Mon Sep 17 00:00:00 2001 From: jpie02 <128189069+jpie02@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:49:47 +0100 Subject: [PATCH 4/5] Replace the tab icon image. --- web_app/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_app/index.html b/web_app/index.html index 2992e560..682cf7ec 100644 --- a/web_app/index.html +++ b/web_app/index.html @@ -2,7 +2,7 @@ - + BookRider From 9522f47a20c26b2729799440275eea618ccd6f12 Mon Sep 17 00:00:00 2001 From: jpie02 <128189069+jpie02@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:37:54 +0100 Subject: [PATCH 5/5] Fix the race condition created when multiple books are selected to be added/removed from a given library in the librarian dashboard. --- web_app/src/Librarian/LibrarianHomePage.tsx | 111 ++++++++++++-------- 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/web_app/src/Librarian/LibrarianHomePage.tsx b/web_app/src/Librarian/LibrarianHomePage.tsx index 085ff501..06748921 100644 --- a/web_app/src/Librarian/LibrarianHomePage.tsx +++ b/web_app/src/Librarian/LibrarianHomePage.tsx @@ -344,36 +344,47 @@ const LibrarianHomePage: React.FC = () => { return; } - try { - const addRequests = selectedBooks.map(id => - fetch(`${API_BASE_URL}/api/books/add-existing/${id}?libraryId=${assignedLibrary.id}`, { + let successCount = 0; + let failCount = 0; + const successfulIds: number[] = []; + + // preventing race conditions on the server by ensuring that the requests are sent sequentially (one by one) + for (const id of selectedBooks) { + try { + const response = await fetch(`${API_BASE_URL}/api/books/add-existing/${id}?libraryId=${assignedLibrary.id}`, { method: "POST", headers: { Authorization: `Bearer ${token}`, }, - }) - ); - - const responses = await Promise.all(addRequests); - - const allSuccessful = responses.every(response => response.ok); - - if (allSuccessful) { - setAddBooksMessage({ - text: "Pomyślnie dodano książki do biblioteki.", - type: "success" - }); - } else { - setAddBooksMessage({ - text: "Niektóre z książek już wcześniej zostały przypisane do Twojej biblioteki.", - type: "error" }); + + if (response.ok) { + successCount++; + successfulIds.push(id); + } else { + failCount++; + } + } catch (error) { + console.error(`Error adding book ${id}:`, error); + failCount++; } + } - setSelectedBooks([]); - } catch { + setSelectedBooks((prev) => prev.filter((id) => !successfulIds.includes(id))); + + if (successCount > 0 && failCount === 0) { + setAddBooksMessage({ + text: "Pomyślnie dodano wszystkie wybrane książki do biblioteki.", + type: "success" + }); + } else if (successCount > 0 && failCount > 0) { + setAddBooksMessage({ + text: `Dodano ${successCount} książek. ${failCount} nie udało się dodać (mogą już istnieć w bibliotece).`, + type: "error" + }); + } else { setAddBooksMessage({ - text: "Wystąpił błąd podczas dodawania książek.", + text: "Nie udało się dodać wybranych książek (mogą już być w bibliotece).", type: "error" }); } @@ -383,37 +394,49 @@ const LibrarianHomePage: React.FC = () => { const token = localStorage.getItem('access_token'); if (!token) return; - try { - const deleteRequests = selectedBooks.map(id => - fetch(`${API_BASE_URL}/api/books/my-library/${id}`, { + let successCount = 0; + let failCount = 0; + const successfulIds: number[] = []; + + // preventing race conditions on the server by ensuring that the requests are sent sequentially (one by one) + for (const id of selectedBooks) { + try { + const response = await fetch(`${API_BASE_URL}/api/books/my-library/${id}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}`, }, - }) - ); + }); - const responses = await Promise.all(deleteRequests); + if (response.ok) { + successCount++; + successfulIds.push(id); + } else { + failCount++; + } + } catch (error) { + console.error(`Error deleting book ${id}:`, error); + failCount++; + } + } - const allSuccessful = responses.every(response => response.ok); + setBookSearchResults(prev => prev.filter(book => !successfulIds.includes(book.id))); - if (allSuccessful) { - setBookSearchResults(prev => prev.filter(book => !selectedBooks.includes(book.id))); + setSelectedBooks((prev) => prev.filter((id) => !successfulIds.includes(id))); - setSelectedBooks([]); - setDeleteBooksMessage({ - text: "Wybrane książki zostały usunięte z biblioteki.", - type: "success" - }); - } else { - setDeleteBooksMessage({ - text: "Niektórych książek nie udało się usunąć.", - type: "error" - }); - } - } catch { + if (successCount > 0 && failCount === 0) { + setDeleteBooksMessage({ + text: "Wybrane książki zostały usunięte z biblioteki.", + type: "success" + }); + } else if (successCount > 0 && failCount > 0) { + setDeleteBooksMessage({ + text: `Usunięto ${successCount} książek. ${failCount} nie udało się usunąć.`, + type: "error" + }); + } else { setDeleteBooksMessage({ - text: "Wystąpił błąd podczas usuwania książek.", + text: "Nie udało się usunąć wybranych książek.", type: "error" }); }