fix appointments and pagination
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user