/*
* Page for booking and managing pet service appointments.
*
* Author: Shiv
* Date: April 2026
*/
"use client";
import dynamic from "next/dynamic";
import { useState, useEffect, useCallback, useRef } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { useAuth } from "@/context/AuthContext";
const API_BASE = "";
const SPECIES_BREEDS = {
Dog: ["Beagle", "Boxer", "Bulldog", "Chihuahua", "Dachshund", "German Shepherd", "Golden Retriever", "Labrador Retriever", "Poodle", "Rottweiler", "Shih Tzu", "Siberian Husky", "Yorkshire Terrier", "Mixed / Other"],
Cat: ["Abyssinian", "Bengal", "British Shorthair", "Maine Coon", "Persian", "Ragdoll", "Scottish Fold", "Siamese", "Sphynx", "Mixed / Other"],
Bird: ["Canary", "Cockatiel", "Cockatoo", "Finch", "Lovebird", "Macaw", "Parakeet", "Parrot", "Other"],
Rabbit: ["Dutch", "Flemish Giant", "Holland Lop", "Lionhead", "Mini Rex", "Other"],
Hamster: ["Dwarf", "Roborovski", "Syrian", "Other"],
"Guinea Pig": ["Abyssinian", "American", "Peruvian", "Teddy", "Other"],
Reptile: ["Ball Python", "Bearded Dragon", "Blue-tongued Skink", "Corn Snake", "Leopard Gecko", "Other"],
Fish: ["Angelfish", "Betta", "Cichlid", "Clownfish", "Goldfish", "Guppy", "Tetra", "Other"],
Other: ["Other"],
};
//Services that only apply to specific species, keyed by species name
const SPECIES_EXCLUSIVE_SERVICES = {
Bird: ["wing clipping", "beak and nail"],
Fish: ["aquarium health"],
};
//Services that are banned for specific species, keyed by species name
const SPECIES_BANNED_SERVICES = {
Bird: ["teeth cleaning"],
};
//Filters out services that are exclusive to a different species, or banned for the selected species.
//When species is unknown, hides all species-exclusive and banned services to avoid invalid options appearing.
function getAvailableServices(services, species) {
const exclusiveKeywords = Object.values(SPECIES_EXCLUSIVE_SERVICES).flat();
const allBannedKeywords = Object.values(SPECIES_BANNED_SERVICES).flat();
if (!species) {
return services.filter((s) => {
const name = s.serviceName.toLowerCase();
return !exclusiveKeywords.some((kw) => name.includes(kw)) &&
!allBannedKeywords.some((kw) => name.includes(kw));
});
}
return services.filter((s) => {
const name = s.serviceName.toLowerCase();
for (const [exclusiveSpecies, keywords] of Object.entries(SPECIES_EXCLUSIVE_SERVICES)) {
if (exclusiveSpecies !== species && keywords.some((kw) => name.includes(kw))) {
return false;
}
}
const banned = SPECIES_BANNED_SERVICES[species] ?? [];
if (banned.some((kw) => name.includes(kw))) {
return false;
}
return true;
});
}
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
//Custom calendar date picker that prevents selecting dates in the past
function DatePicker({ value, minDate, onChange }) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const min = minDate ? new Date(minDate + "T00:00:00") : today;
const parsed = value ? new Date(value + "T00:00:00") : null;
const [viewYear, setViewYear] = useState(parsed ? parsed.getFullYear() : min.getFullYear());
const [viewMonth, setViewMonth] = useState(parsed ? parsed.getMonth() : min.getMonth());
function prevMonth() {
if (viewMonth === 0) { setViewMonth(11); setViewYear((y) => y - 1); }
else { setViewMonth((m) => m - 1); }
}
function nextMonth() {
if (viewMonth === 11) { setViewMonth(0); setViewYear((y) => y + 1); }
else { setViewMonth((m) => m + 1); }
}
const firstDay = new Date(viewYear, viewMonth, 1).getDay();
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
const minYear = min.getFullYear();
const minMonth = min.getMonth();
const isPrevDisabled = viewYear < minYear || (viewYear === minYear && viewMonth <= minMonth);
function selectDay(day) {
const d = new Date(viewYear, viewMonth, day);
if (d < min) return;
const iso = `${viewYear}-${String(viewMonth + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
onChange(iso);
}
function isSelected(day) {
if (!parsed) return false;
return parsed.getFullYear() === viewYear && parsed.getMonth() === viewMonth && parsed.getDate() === day;
}
function isDisabled(day) {
return new Date(viewYear, viewMonth, day) < min;
}
const cells = [];
for (let i = 0; i < firstDay; i++) {
cells.push({ key: `empty-${viewYear}-${viewMonth}-${String(i)}`, day: null });
}
for (let d = 1; d <= daysInMonth; d++) {
cells.push({ key: `day-${viewYear}-${viewMonth}-${String(d)}`, day: d });
}
return (
{MONTHS[viewMonth]} {viewYear}
{DAYS.map((d) => (
{d}
))}
{cells.map(({ key, day }) =>
day === null ? (
) : (
)
)}
{parsed && (
Selected: {MONTHS[parsed.getMonth()]} {parsed.getDate()}, {parsed.getFullYear()}
)}
);
}
const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[#444]";
const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]";
const selectCls = `custom-select ${inputCls} bg-white cursor-pointer`;
const errorCls = "bg-[#fff0f0] border border-[#f5c6c6] text-[#c0392b] rounded-lg px-4 py-3 text-[0.9rem]";
const successCls = "bg-[#f0fdf4] border border-[#bbf7d0] text-[#16a34a] rounded-lg px-4 py-3 text-[0.9rem]";
const submitBtnCls = "py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed";
//Modal dialog for quickly adding a new pet without leaving the appointments page
function AddPetModal({ token, onClose, onAdded }) {
const [petName, setPetName] = useState("");
const [species, setSpecies] = useState("");
const [breed, setBreed] = useState("");
const [submitting, setSubmitting] = useState(false);
const [petError, setPetError] = useState(null);
async function handleSubmit(e) {
e.preventDefault();
setPetError(null);
setSubmitting(true);
try {
const res = await fetch(`${API_BASE}/api/v1/my-pets`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ petName, species, breed: breed || null }),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
throw new Error(data?.message || `Request failed (${res.status})`);
}
onAdded();
onClose();
}
catch (err) {
setPetError(err.message);
}
finally {
setSubmitting(false);
}
}
return (
e.stopPropagation()}>
Add a New Pet
{petError &&
{petError}
}
);
}
//Appointments page - book a service or adoption, and view past and active appointments
function AppointmentsPage() {
const { user, token, loading: authLoading } = useAuth();
const router = useRouter();
const searchParams = useSearchParams();
const preselectedPetId = searchParams.get("petId");
const adoptionMode = searchParams.get("adoptionMode") === "true";
const adoptionPetId = searchParams.get("petId");
const adoptionPetName = searchParams.get("petName") || "";
const adoptionPetSpecies = searchParams.get("petSpecies") || "";
const adoptionPetBreed = searchParams.get("petBreed") || "";
const adoptionStoreId = searchParams.get("storeId") || "";
const adoptionStoreName = searchParams.get("storeName") || "";
const didPreselectRef = useRef(false);
const errorRef = useRef(null);
const historyRef = useRef(null);
const [adoptionVerified, setAdoptionVerified] = useState(!adoptionMode);
const [adoptionVerifyError, setAdoptionVerifyError] = useState(null);
const [adoptionVerifyLoading, setAdoptionVerifyLoading] = useState(adoptionMode);
const [stores, setStores] = useState([]);
const [employees, setEmployees] = useState([]);
const [services, setServices] = useState([]);
const [allPets, setAllPets] = useState([]);
const [customerPets, setCustomerPets] = useState([]);
const [availableSlots, setAvailableSlots] = useState([]);
const [storeId, setStoreId] = useState("");
const [serviceId, setServiceId] = useState("");
const [employeeId, setEmployeeId] = useState("");
const [appointmentDate, setAppointmentDate] = useState("");
const [appointmentTime, setAppointmentTime] = useState("");
const [selectedPetIds, setSelectedPetIds] = useState([]);
const [loadingSlots, setLoadingSlots] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(null);
useEffect(() => {
if (error && errorRef.current) {
errorRef.current.scrollIntoView({ behavior: "smooth", block: "center" });
}
}, [error]);
const [appointments, setAppointments] = useState([]);
const [loadingAppointments, setLoadingAppointments] = useState(false);
const [adoptions, setAdoptions] = useState([]);
const [loadingAdoptions, setLoadingAdoptions] = useState(false);
const [showAddPetModal, setShowAddPetModal] = useState(false);
const [cancellingId, setCancellingId] = useState(null);
const [apptSearch, setApptSearch] = useState("");
const [adoptionSearch, setAdoptionSearch] = useState("");
const [showPastAppts, setShowPastAppts] = useState(false);
const [showPastAdoptions, setShowPastAdoptions] = useState(false);
//Pagination state for each of the four history lists
const HISTORY_PAGE_SIZE = 5;
const [apptPage, setApptPage] = useState(0);
const [pastApptPage, setPastApptPage] = useState(0);
const [adoptionPage, setAdoptionPage] = useState(0);
const [pastAdoptionPage, setPastAdoptionPage] = useState(0);
//Reset appointment page to 0 when the search text changes
useEffect(() => { setApptPage(0); }, [apptSearch]);
//Reset adoption page to 0 when the search text changes
useEffect(() => { setAdoptionPage(0); }, [adoptionSearch]);
const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
useEffect(() => {
if (!authLoading && !user) {
const target = preselectedPetId ? `/appointments?petId=${encodeURIComponent(preselectedPetId)}` : "/appointments";
router.push(`/login?next=${encodeURIComponent(target)}`);
}
}, [authLoading, user, router, preselectedPetId]);
useEffect(() => {
if (!adoptionMode || !adoptionPetId) return;
setAdoptionVerifyLoading(true);
fetch(`${API_BASE}/api/v1/pets/${adoptionPetId}`)
.then((r) => {
if (!r.ok) throw new Error("Pet not found. This link may be invalid.");
return r.json();
})
.then((pet) => {
if (pet.petStatus?.toLowerCase() !== "available") {
throw new Error(`${pet.petName || "This pet"} is no longer available for adoption (status: ${pet.petStatus}).`);
}
if (adoptionStoreId && String(pet.storeId) !== String(adoptionStoreId)) {
throw new Error("Store mismatch: this pet is not located at the specified store.");
}
setAdoptionVerified(true);
})
.catch((err) => setAdoptionVerifyError(err.message))
.finally(() => setAdoptionVerifyLoading(false));
}, [adoptionMode, adoptionPetId, adoptionStoreId]);
//Loads the user's registered pets for the pet selector
const loadCustomerPets = useCallback(() => {
if (!token || !canBookAppointments) return;
fetch(`${API_BASE}/api/v1/my-pets`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then((data) => setCustomerPets(Array.isArray(data) ? data : []))
.catch(() => {});
}, [token, canBookAppointments]);
useEffect(() => {
if (!token) return;
fetch(`${API_BASE}/api/v1/dropdowns/stores`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then(setStores)
.catch(() => setError("Failed to load stores."));
fetch(`${API_BASE}/api/v1/services?size=100`)
.then((r) => r.json())
.then((data) => setServices(data.content ?? []))
.catch(() => setError("Failed to load services."));
fetch(`${API_BASE}/api/v1/pets?size=200&sort=id,asc&status=Available`)
.then((r) => r.json())
.then((data) => setAllPets(data.content ?? []))
.catch(() => {});
loadCustomerPets();
}, [token, loadCustomerPets]);
useEffect(() => {
if (didPreselectRef.current) return;
if (adoptionMode) {
if (adoptionStoreId && services.length > 0) {
setStoreId(adoptionStoreId);
const adoptionSvc =
services.find((s) => s.serviceName.toLowerCase().includes("adopt")) ||
services[0];
if (adoptionSvc) {
setServiceId(String(adoptionSvc.serviceId));
didPreselectRef.current = true;
}
}
return;
}
if (!preselectedPetId || services.length === 0 || allPets.length === 0) return;
const adoptionSvc = services.find((s) => s.serviceName.toLowerCase().includes("adopt"));
if (adoptionSvc) {
setServiceId(String(adoptionSvc.serviceId));
}
setSelectedPetIds([Number(preselectedPetId)]);
didPreselectRef.current = true;
}, [adoptionMode, adoptionStoreId, preselectedPetId, services, allPets]);
//Fetches the user's booked appointments
const loadAppointments = useCallback(() => {
if (!token) return;
setLoadingAppointments(true);
fetch(`${API_BASE}/api/v1/appointments?size=50&sort=appointmentDate,desc`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then((data) => setAppointments(data.content ?? []))
.catch(() => {})
.finally(() => setLoadingAppointments(false));
}, [token]);
//Fetches the user's adoption requests
const loadAdoptions = useCallback(() => {
if (!token) return;
setLoadingAdoptions(true);
fetch(`${API_BASE}/api/v1/adoptions?size=50&sort=adoptionDate,desc`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then((data) => setAdoptions(data.content ?? []))
.catch(() => {})
.finally(() => setLoadingAdoptions(false));
}, [token]);
useEffect(() => {
loadAppointments();
loadAdoptions();
}, [loadAppointments, loadAdoptions]);
//Cancels an appointment after asking the user to confirm
async function handleCancelAppointment(appointmentId) {
if (!confirm("Cancel this appointment?")) return;
setCancellingId(appointmentId);
try {
const res = await fetch(`${API_BASE}/api/v1/appointments/${appointmentId}/cancel`, {
method: "PATCH",
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
const data = await res.json().catch(() => null);
throw new Error(data?.message || `Failed to cancel appointment (${res.status})`);
}
loadAppointments();
} catch (err) {
alert(err.message);
} finally {
setCancellingId(null);
}
}
//Cancels an adoption request after asking the user to confirm
async function handleCancelAdoption(adoptionId) {
if (!confirm("Cancel this adoption request?")) return;
setCancellingId(adoptionId);
try {
const res = await fetch(`${API_BASE}/api/v1/adoptions/${adoptionId}/cancel`, {
method: "PATCH",
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
const data = await res.json().catch(() => null);
throw new Error(data?.message || `Failed to cancel adoption (${res.status})`);
}
loadAdoptions();
} catch (err) {
alert(err.message);
} finally {
setCancellingId(null);
}
}
useEffect(() => {
if (!token || !storeId) {
setEmployees([]);
setEmployeeId("");
return;
}
fetch(`${API_BASE}/api/v1/dropdowns/stores/${storeId}/employees`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then((data) => setEmployees(Array.isArray(data) ? data : []))
.catch(() => setEmployees([]));
}, [token, storeId]);
useEffect(() => {
if (!employees.length) { setEmployeeId(""); return; }
const currentExists = employees.some((employee) => String(employee.id) === String(employeeId));
if (!currentExists) {
setEmployeeId(String(employees[0].id));
}
}, [employees, employeeId]);
useEffect(() => {
if (!storeId || !serviceId || !appointmentDate) {
setAvailableSlots([]);
setAppointmentTime("");
return;
}
setLoadingSlots(true);
setAppointmentTime("");
const params = new URLSearchParams({ storeId, serviceId, date: appointmentDate });
fetch(`${API_BASE}/api/v1/appointments/availability?${params}`)
.then((r) => {
if (!r.ok) throw new Error("Failed to check availability");
return r.json();
})
.then(setAvailableSlots)
.catch(() => setAvailableSlots([]))
.finally(() => setLoadingSlots(false));
}, [storeId, serviceId, appointmentDate]);
const eligiblePets = customerPets.filter(
(p) => p.petStatus?.toLowerCase() === "owned" || p.petStatus?.toLowerCase() === "adopted"
);
const selectedService = services.find((s) => s.serviceId === Number(serviceId));
const selectedPet = !adoptionMode
? (eligiblePets.find((p) => p.customerPetId === selectedPetIds[0]) || null)
: null;
const availableServices = getAvailableServices(services, selectedPet?.species);
function handleServiceChange(newServiceId) {
setServiceId(newServiceId);
}
//Selects a pet and clears the chosen service if it is not valid for that species
function handlePetSelect(petId) {
const newPet = eligiblePets.find((p) => p.customerPetId === petId);
setSelectedPetIds([petId]);
if (serviceId && newPet) {
const newAvailable = getAvailableServices(services, newPet.species);
if (!newAvailable.some((s) => String(s.serviceId) === String(serviceId))) {
setServiceId("");
setAppointmentTime("");
setAvailableSlots([]);
}
}
}
//Converts a 24-hour time string to 12-hour AM/PM format
function formatTime(timeStr) {
const [h, m] = timeStr.split(":");
const hour = parseInt(h, 10);
const ampm = hour >= 12 ? "PM" : "AM";
const display = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
return `${display}:${m} ${ampm}`;
}
function getMinDate() {
const d = new Date();
return d.toISOString().split("T")[0];
}
const formValid = adoptionMode
? Boolean(employeeId && appointmentDate && adoptionVerified)
: storeId && serviceId && appointmentDate && appointmentTime && selectedPetIds.length > 0;
//Submits either a new appointment or an adoption request depending on the current mode
async function handleSubmit(e) {
e.preventDefault();
setError(null);
setSuccess(null);
if (!canBookAppointments) {
setError("Only customer accounts can book appointments from the web app.");
return;
}
if (!adoptionMode && selectedPetIds.length === 0) {
setError("Please select a pet for your appointment.");
return;
}
if (!adoptionMode && selectedPet && selectedPet.petStatus?.toLowerCase() !== "owned" && selectedPet.petStatus?.toLowerCase() !== "adopted") {
setError("The selected pet is no longer eligible for appointments. Please refresh the page.");
return;
}
if (!adoptionMode && selectedPet && serviceId) {
const chosenService = services.find((s) => String(s.serviceId) === String(serviceId));
if (chosenService && getAvailableServices([chosenService], selectedPet.species).length === 0) {
setError(`"${chosenService.serviceName}" is not available for ${selectedPet.species}s. Please select a valid service.`);
return;
}
}
setSubmitting(true);
try {
if (adoptionMode) {
const body = {
petId: Number(adoptionPetId),
employeeId: employeeId ? Number(employeeId) : undefined,
sourceStoreId: adoptionStoreId ? Number(adoptionStoreId) : undefined,
adoptionDate: appointmentDate,
};
const res = await fetch(`${API_BASE}/api/v1/adoptions/request`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
throw new Error(data?.message || data?.error || `Request failed (${res.status})`);
}
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;
}
const body = {
customerId: user.customerId || user.id,
storeId: Number(storeId),
serviceId: Number(serviceId),
employeeId: employeeId ? Number(employeeId) : undefined,
appointmentDate,
appointmentTime: appointmentTime + ":00",
appointmentStatus: "Booked",
};
if (selectedPetIds.length > 0) {
body.petId = selectedPetIds[0];
}
const res = await fetch(`${API_BASE}/api/v1/appointments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
throw new Error(data?.message || data?.error || `Request failed (${res.status})`);
}
setSuccess("Appointment booked successfully!");
setStoreId("");
setServiceId("");
setAppointmentDate("");
setAppointmentTime("");
setSelectedPetIds([]);
setAvailableSlots([]);
loadAppointments();
setTimeout(() => historyRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 300);
}
catch (err) {
setError(err.message);
}
finally {
setSubmitting(false);
}
}
if (authLoading) {
return (
Loading...
);
}
if (!user) return null;
return (
{showAddPetModal && (
setShowAddPetModal(false)}
onAdded={loadCustomerPets}
/>
)}
{adoptionMode ? "Schedule an Adoption" : "Schedule an Appointment"}
{adoptionMode ? "Schedule a pet adoption visit" : "Book a service for your pet."}
{canBookAppointments ? (
) : null}
{/* History panel */}
{canBookAppointments ? "Your Appointments" : "Appointments"}
{loadingAppointments ? (
Loading appointments...
) : (() => {
const activeAppts = appointments.filter((a) => a.appointmentStatus?.toLowerCase() === "booked");
const pastAppts = appointments.filter((a) => a.appointmentStatus?.toLowerCase() !== "booked");
const q = apptSearch.toLowerCase();
const filteredActive = activeAppts.filter((a) =>
!q || [a.serviceName, a.storeName, a.petName].some((v) => v?.toLowerCase().includes(q))
);
//Paginate active appointments
const apptTotalPages = Math.ceil(filteredActive.length / HISTORY_PAGE_SIZE);
const apptSlice = filteredActive.slice(apptPage * HISTORY_PAGE_SIZE, (apptPage + 1) * HISTORY_PAGE_SIZE);
//Paginate past appointments
const pastApptTotalPages = Math.ceil(pastAppts.length / HISTORY_PAGE_SIZE);
const pastApptSlice = pastAppts.slice(pastApptPage * HISTORY_PAGE_SIZE, (pastApptPage + 1) * HISTORY_PAGE_SIZE);
return (
<>
setApptSearch(e.target.value)}
/>
{filteredActive.length === 0 ? (
{activeAppts.length === 0 ? "No active appointments." : "No results."}
) : (
<>
{apptSlice.map((a) => (
{a.serviceName}
{a.appointmentStatus}
{a.storeName}
{a.appointmentDate} at {formatTime(a.appointmentTime)}
{a.petName && (
Pet: {a.petName}
)}
))}
{apptTotalPages > 1 && (
Page {apptPage + 1} of {apptTotalPages}
)}
>
)}
{pastAppts.length > 0 && (
{showPastAppts && (
<>
{pastApptSlice.map((a) => (
{a.serviceName}
{a.appointmentStatus}
{a.storeName}
{a.appointmentDate} at {formatTime(a.appointmentTime)}
{a.petName && (
Pet: {a.petName}
)}
))}
{pastApptTotalPages > 1 && (
Page {pastApptPage + 1} of {pastApptTotalPages}
)}
>
)}
)}
>
);
})()}
{canBookAppointments ? "Your Adoptions" : "Adoptions"}
{loadingAdoptions ? (
Loading adoptions...
) : (() => {
const activeAdoptions = adoptions.filter((a) => a.adoptionStatus?.toLowerCase() === "pending");
const pastAdoptions = adoptions.filter((a) => a.adoptionStatus?.toLowerCase() !== "pending");
const q = adoptionSearch.toLowerCase();
const filteredActive = activeAdoptions.filter((a) =>
!q || [a.petName, a.sourceStoreName].some((v) => v?.toLowerCase().includes(q))
);
//Paginate active adoptions
const adoptionTotalPages = Math.ceil(filteredActive.length / HISTORY_PAGE_SIZE);
const adoptionSlice = filteredActive.slice(adoptionPage * HISTORY_PAGE_SIZE, (adoptionPage + 1) * HISTORY_PAGE_SIZE);
//Paginate past adoptions
const pastAdoptionTotalPages = Math.ceil(pastAdoptions.length / HISTORY_PAGE_SIZE);
const pastAdoptionSlice = pastAdoptions.slice(pastAdoptionPage * HISTORY_PAGE_SIZE, (pastAdoptionPage + 1) * HISTORY_PAGE_SIZE);
return (
<>
setAdoptionSearch(e.target.value)}
/>
{filteredActive.length === 0 ? (
{activeAdoptions.length === 0 ? "No active adoption requests." : "No results."}
) : (
<>
{adoptionSlice.map((a) => (
{a.petName}
{a.adoptionStatus}
{a.sourceStoreName}
{a.adoptionDate}
))}
{adoptionTotalPages > 1 && (
Page {adoptionPage + 1} of {adoptionTotalPages}
)}
>
)}
{pastAdoptions.length > 0 && (
{showPastAdoptions && (
<>
{pastAdoptionSlice.map((a) => (
{a.petName}
{a.adoptionStatus}
{a.sourceStoreName}
{a.adoptionDate}
))}
{pastAdoptionTotalPages > 1 && (
Page {pastAdoptionPage + 1} of {pastAdoptionTotalPages}
)}
>
)}
)}
>
);
})()}
);
}
export default dynamic(() => Promise.resolve(AppointmentsPage), {
ssr: false,
});