fix appointments and pagination

This commit is contained in:
2026-04-15 06:52:10 -06:00
parent 7ad35bd2dc
commit fb2b070e32
4 changed files with 68 additions and 9 deletions

View File

@@ -74,13 +74,20 @@ export default function AdoptPage() {
[pets] [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), () => (selectedBreed ? pets.filter((p) => p.petBreed === selectedBreed) : pets),
[pets, selectedBreed] [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) { function handleSearch(e) {
e.preventDefault(); e.preventDefault();
setCurrentPage(0);
setQuery(search.trim()); setQuery(search.trim());
} }
@@ -89,6 +96,7 @@ export default function AdoptPage() {
setQuery(""); setQuery("");
setSelectedSpecies(""); setSelectedSpecies("");
setSelectedBreed(""); setSelectedBreed("");
setCurrentPage(0);
} }
const hasActiveFilters = query || selectedSpecies || selectedBreed; const hasActiveFilters = query || selectedSpecies || selectedBreed;
@@ -109,7 +117,7 @@ export default function AdoptPage() {
id="species-filter" id="species-filter"
className="adopt-filter-select" className="adopt-filter-select"
value={selectedSpecies} value={selectedSpecies}
onChange={(e) => setSelectedSpecies(e.target.value)} onChange={(e) => { setSelectedSpecies(e.target.value); setCurrentPage(0); }}
> >
<option value="">All Species</option> <option value="">All Species</option>
{speciesOptions.map((s) => ( {speciesOptions.map((s) => (
@@ -124,7 +132,7 @@ export default function AdoptPage() {
id="breed-filter" id="breed-filter"
className="adopt-filter-select" className="adopt-filter-select"
value={selectedBreed} value={selectedBreed}
onChange={(e) => setSelectedBreed(e.target.value)} onChange={(e) => { setSelectedBreed(e.target.value); setCurrentPage(0); }}
disabled={!selectedSpecies} disabled={!selectedSpecies}
> >
<option value="">{!selectedSpecies ? "Select a species first" : "All Breeds"}</option> <option value="">{!selectedSpecies ? "Select a species first" : "All Breeds"}</option>
@@ -184,6 +192,25 @@ export default function AdoptPage() {
))} ))}
</div> </div>
)} )}
{!loading && !error && totalPages > 1 && (
<div className="pagination-controls">
<button
className="pagination-btn"
onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}
disabled={currentPage === 0}
>
Prev
</button>
<span className="pagination-info">Page {currentPage + 1} of {totalPages}</span>
<button
className="pagination-btn"
onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}
disabled={currentPage === totalPages - 1}
>
Next
</button>
</div>
)}
</section> </section>
</main> </main>
); );

View File

@@ -341,6 +341,7 @@ function AppointmentsPage() {
const didPreselectRef = useRef(false); const didPreselectRef = useRef(false);
const errorRef = useRef(null); const errorRef = useRef(null);
const historyRef = useRef(null);
// Adoption-mode URL verification // Adoption-mode URL verification
const [adoptionVerified, setAdoptionVerified] = useState(!adoptionMode); const [adoptionVerified, setAdoptionVerified] = useState(!adoptionMode);
@@ -507,9 +508,9 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
}, [token]); }, [token]);
useEffect(() => { useEffect(() => {
if (adoptionMode) loadAdoptions(); loadAppointments();
else loadAppointments(); loadAdoptions();
}, [adoptionMode, loadAppointments, loadAdoptions]); }, [loadAppointments, loadAdoptions]);
async function handleCancelAppointment(appointmentId) { async function handleCancelAppointment(appointmentId) {
if (!confirm("Cancel this appointment?")) return; 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.`); setSuccess(`Adoption request submitted! ${adoptionPetName} is now marked as Pending. We'll be in touch soon.`);
setEmployeeId(""); setEmployeeId("");
loadAdoptions(); loadAdoptions();
setTimeout(() => historyRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 300);
return; return;
} }
@@ -744,6 +746,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
setSelectedPetIds([]); setSelectedPetIds([]);
setAvailableSlots([]); setAvailableSlots([]);
loadAppointments(); loadAppointments();
setTimeout(() => historyRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 300);
} }
catch (err) { catch (err) {
@@ -957,7 +960,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
</form> </form>
) : null} ) : null}
<div className="appt-history"> <div className="appt-history" ref={historyRef}>
<h2 className="appt-form-title"> <h2 className="appt-form-title">
{adoptionMode ? "Your Adoptions" : canBookAppointments ? "Your Appointments" : "Appointments"} {adoptionMode ? "Your Adoptions" : canBookAppointments ? "Your Appointments" : "Appointments"}
</h2> </h2>

View File

@@ -3030,3 +3030,7 @@ img, video, iframe {
.contact-submit-btn:disabled { opacity: 0.6; cursor: not-allowed; } .contact-submit-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.contact-error { color: #c0392b; font-size: 0.9rem; } .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; } .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; }

View File

@@ -14,10 +14,13 @@ export default function ProductsPage() {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const PAGE_SIZE = 100; const PAGE_SIZE = 100;
const ITEMS_PER_PAGE = 20;
const [currentPage, setCurrentPage] = useState(0);
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
setError(null); setError(null);
setCurrentPage(0);
fetchAllPages((page) => { fetchAllPages((page) => {
const params = new URLSearchParams({ const params = new URLSearchParams({
@@ -37,10 +40,14 @@ export default function ProductsPage() {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [query]); }, [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) { function handleSearch(e) {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
setError(null); setError(null);
setCurrentPage(0);
setQuery(search.trim()); setQuery(search.trim());
} }
@@ -92,7 +99,7 @@ export default function ProductsPage() {
{!loading && !error && products.length > 0 && ( {!loading && !error && products.length > 0 && (
<div className="adopt-grid"> <div className="adopt-grid">
{products.map((product) => ( {displayedProducts.map((product) => (
<ProductCard <ProductCard
key={product.prodId} key={product.prodId}
prodId={product.prodId} prodId={product.prodId}
@@ -104,7 +111,25 @@ export default function ProductsPage() {
))} ))}
</div> </div>
)} )}
{!loading && !error && totalPages > 1 && (
<div className="pagination-controls">
<button
className="pagination-btn"
onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}
disabled={currentPage === 0}
>
Prev
</button>
<span className="pagination-info">Page {currentPage + 1} of {totalPages}</span>
<button
className="pagination-btn"
onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}
disabled={currentPage === totalPages - 1}
>
Next
</button>
</div>
)}
</section> </section>
</main> </main>
); );