From 0a309598fc910393b3f765a58a49887221d2f978 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 15 Apr 2026 06:52:10 -0600 Subject: [PATCH] fix appointments and pagination --- web/app/adopt/page.js | 33 ++++++++++++++++++++++++++++++--- web/app/appointments/page.js | 11 +++++++---- web/app/globals.css | 4 ++++ web/app/products/page.js | 29 +++++++++++++++++++++++++++-- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/web/app/adopt/page.js b/web/app/adopt/page.js index 3f364f94..3eb8c880 100644 --- a/web/app/adopt/page.js +++ b/web/app/adopt/page.js @@ -74,13 +74,20 @@ export default function AdoptPage() { [pets] ); - const displayedPets = useMemo( + const ITEMS_PER_PAGE = 20; + const [currentPage, setCurrentPage] = useState(0); + + const filteredPets = useMemo( () => (selectedBreed ? pets.filter((p) => p.petBreed === selectedBreed) : pets), [pets, selectedBreed] ); + const totalPages = Math.ceil(filteredPets.length / ITEMS_PER_PAGE); + const displayedPets = filteredPets.slice(currentPage * ITEMS_PER_PAGE, (currentPage + 1) * ITEMS_PER_PAGE); + function handleSearch(e) { e.preventDefault(); + setCurrentPage(0); setQuery(search.trim()); } @@ -89,6 +96,7 @@ export default function AdoptPage() { setQuery(""); setSelectedSpecies(""); setSelectedBreed(""); + setCurrentPage(0); } const hasActiveFilters = query || selectedSpecies || selectedBreed; @@ -109,7 +117,7 @@ export default function AdoptPage() { id="species-filter" className="adopt-filter-select" value={selectedSpecies} - onChange={(e) => setSelectedSpecies(e.target.value)} + onChange={(e) => { setSelectedSpecies(e.target.value); setCurrentPage(0); }} > {speciesOptions.map((s) => ( @@ -124,7 +132,7 @@ export default function AdoptPage() { id="breed-filter" className="adopt-filter-select" value={selectedBreed} - onChange={(e) => setSelectedBreed(e.target.value)} + onChange={(e) => { setSelectedBreed(e.target.value); setCurrentPage(0); }} disabled={!selectedSpecies} > @@ -184,6 +192,25 @@ export default function AdoptPage() { ))} )} + {!loading && !error && totalPages > 1 && ( +
+ + Page {currentPage + 1} of {totalPages} + +
+ )} ); diff --git a/web/app/appointments/page.js b/web/app/appointments/page.js index ef3d8e48..2dffe6bb 100644 --- a/web/app/appointments/page.js +++ b/web/app/appointments/page.js @@ -341,6 +341,7 @@ function AppointmentsPage() { const didPreselectRef = useRef(false); const errorRef = useRef(null); + const historyRef = useRef(null); // Adoption-mode URL verification const [adoptionVerified, setAdoptionVerified] = useState(!adoptionMode); @@ -507,9 +508,9 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; }, [token]); useEffect(() => { - if (adoptionMode) loadAdoptions(); - else loadAppointments(); - }, [adoptionMode, loadAppointments, loadAdoptions]); + loadAppointments(); + loadAdoptions(); + }, [loadAppointments, loadAdoptions]); async function handleCancelAppointment(appointmentId) { if (!confirm("Cancel this appointment?")) return; @@ -704,6 +705,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; setSuccess(`Adoption request submitted! ${adoptionPetName} is now marked as Pending. We'll be in touch soon.`); setEmployeeId(""); loadAdoptions(); + setTimeout(() => historyRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 300); return; } @@ -744,6 +746,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; setSelectedPetIds([]); setAvailableSlots([]); loadAppointments(); + setTimeout(() => historyRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 300); } catch (err) { @@ -957,7 +960,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; ) : null} -
+

{adoptionMode ? "Your Adoptions" : canBookAppointments ? "Your Appointments" : "Appointments"}

diff --git a/web/app/globals.css b/web/app/globals.css index d5b8aa87..0e80a955 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -3030,3 +3030,7 @@ img, video, iframe { .contact-submit-btn:disabled { opacity: 0.6; cursor: not-allowed; } .contact-error { color: #c0392b; font-size: 0.9rem; } .contact-success { color: #166534; background: #dcfce7; border: 1px solid #bbf7d0; border-radius: 8px; padding: 0.75rem 1rem; } +.pagination-controls { display: flex; align-items: center; justify-content: center; gap: 1rem; padding: 1.5rem 1rem; } +.pagination-btn { background: #333; color: white; border: none; border-radius: 8px; padding: 0.5rem 1.2rem; font-size: 0.9rem; font-weight: 600; cursor: pointer; } +.pagination-btn:disabled { background: #ccc; cursor: not-allowed; } +.pagination-info { font-size: 0.9rem; color: #555; font-weight: 500; } diff --git a/web/app/products/page.js b/web/app/products/page.js index 19cf8670..2a30e038 100644 --- a/web/app/products/page.js +++ b/web/app/products/page.js @@ -14,10 +14,13 @@ export default function ProductsPage() { const [query, setQuery] = useState(""); const PAGE_SIZE = 100; + const ITEMS_PER_PAGE = 20; + const [currentPage, setCurrentPage] = useState(0); useEffect(() => { setLoading(true); setError(null); + setCurrentPage(0); fetchAllPages((page) => { const params = new URLSearchParams({ @@ -37,10 +40,14 @@ export default function ProductsPage() { .finally(() => setLoading(false)); }, [query]); + const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE); + const displayedProducts = products.slice(currentPage * ITEMS_PER_PAGE, (currentPage + 1) * ITEMS_PER_PAGE); + function handleSearch(e) { e.preventDefault(); setLoading(true); setError(null); + setCurrentPage(0); setQuery(search.trim()); } @@ -92,7 +99,7 @@ export default function ProductsPage() { {!loading && !error && products.length > 0 && (
- {products.map((product) => ( + {displayedProducts.map((product) => ( )} - + {!loading && !error && totalPages > 1 && ( +
+ + Page {currentPage + 1} of {totalPages} + +
+ )} );