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 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" }); } diff --git a/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx b/web_app/src/LibraryAdmin/LibraryAdminAddLibrarian.tsx index ccfd4905..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.'); @@ -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..cd02983a 100644 --- a/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx +++ b/web_app/src/LibraryAdmin/LibraryAdminHomePage.tsx @@ -10,6 +10,14 @@ interface Librarian { lastName: string; } +interface PasswordResetResponse { + id: string; + username: string; + firstName: string; + lastName: string; + tempPassword: string; +} + const LibraryAdminHomePage: React.FC = () => { const [usernameSearch, setUsernameSearch] = useState(''); const [librarians, setLibrarians] = useState([]); @@ -72,6 +80,10 @@ const LibraryAdminHomePage: React.FC = () => { }; const resetPassword = async (username: string) => { + if (!window.confirm(`Czy na pewno chcesz zresetować hasło dla użytkownika "${username}"?`)) { + return; + } + const token = localStorage.getItem('access_token'); try { const res = await fetch(`${API_BASE_URL}/api/library-admins/librarians/reset-password/${username}`, { @@ -80,7 +92,15 @@ const LibraryAdminHomePage: React.FC = () => { Authorization: `Bearer ${token}`, }, }); - setMessage(res.ok ? 'Hasło bibliotekarza zresetowano pomyślnie.' : 'Nie udało się zresetować hasła bibliotekarza.'); + + if (res.ok) { + const data: PasswordResetResponse = await res.json(); + const newPassword = data.tempPassword; + + setMessage(`Hasło zresetowane dla ${username}. Nowe hasło: ${newPassword}`); + } else { + setMessage('Nie udało się zresetować hasła bibliotekarza.'); + } } catch (err) { console.error(err); setMessage('Error resetting password'); @@ -88,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}`, { @@ -98,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.'); } @@ -160,7 +184,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}

}