Comments, appointments adjustments, fixed some issues
This commit is contained in:
@@ -20,13 +20,31 @@ const SPECIES_BREEDS = {
|
||||
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) {
|
||||
if (!species) return services;
|
||||
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)) {
|
||||
@@ -34,6 +52,10 @@ function getAvailableServices(services, species) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const banned = SPECIES_BANNED_SERVICES[species] ?? [];
|
||||
if (banned.some((kw) => name.includes(kw))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -41,6 +63,7 @@ function getAvailableServices(services, species) {
|
||||
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);
|
||||
@@ -137,6 +160,7 @@ const errorCls = "bg-[#fff0f0] border border-[#f5c6c6] text-[#c0392b] rounded-lg
|
||||
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("");
|
||||
@@ -219,6 +243,7 @@ function AddPetModal({ token, onClose, onAdded }) {
|
||||
);
|
||||
}
|
||||
|
||||
//Appointments page - book a service or adoption, and view past and active appointments
|
||||
function AppointmentsPage() {
|
||||
const { user, token, loading: authLoading } = useAuth();
|
||||
const router = useRouter();
|
||||
@@ -279,6 +304,18 @@ function AppointmentsPage() {
|
||||
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(() => {
|
||||
@@ -309,6 +346,7 @@ function AppointmentsPage() {
|
||||
.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`, {
|
||||
@@ -369,6 +407,7 @@ function AppointmentsPage() {
|
||||
didPreselectRef.current = true;
|
||||
}, [adoptionMode, adoptionStoreId, preselectedPetId, services, allPets]);
|
||||
|
||||
//Fetches the user's booked appointments
|
||||
const loadAppointments = useCallback(() => {
|
||||
if (!token) return;
|
||||
setLoadingAppointments(true);
|
||||
@@ -381,6 +420,7 @@ function AppointmentsPage() {
|
||||
.finally(() => setLoadingAppointments(false));
|
||||
}, [token]);
|
||||
|
||||
//Fetches the user's adoption requests
|
||||
const loadAdoptions = useCallback(() => {
|
||||
if (!token) return;
|
||||
setLoadingAdoptions(true);
|
||||
@@ -398,6 +438,7 @@ function AppointmentsPage() {
|
||||
loadAdoptions();
|
||||
}, [loadAppointments, loadAdoptions]);
|
||||
|
||||
//Cancels an appointment after asking the user to confirm
|
||||
async function handleCancelAppointment(appointmentId) {
|
||||
if (!confirm("Cancel this appointment?")) return;
|
||||
setCancellingId(appointmentId);
|
||||
@@ -418,6 +459,7 @@ function AppointmentsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
//Cancels an adoption request after asking the user to confirm
|
||||
async function handleCancelAdoption(adoptionId) {
|
||||
if (!confirm("Cancel this adoption request?")) return;
|
||||
setCancellingId(adoptionId);
|
||||
@@ -494,6 +536,7 @@ function AppointmentsPage() {
|
||||
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]);
|
||||
@@ -507,6 +550,7 @@ function AppointmentsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
//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);
|
||||
@@ -524,6 +568,7 @@ function AppointmentsPage() {
|
||||
? 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);
|
||||
@@ -749,7 +794,7 @@ function AppointmentsPage() {
|
||||
<option value="">Select a service...</option>
|
||||
{availableServices.map((s) => (
|
||||
<option key={s.serviceId} value={s.serviceId}>
|
||||
{s.serviceName} — ${Number(s.servicePrice).toFixed(2)} ({s.serviceDuration} min)
|
||||
{s.serviceName} - ${Number(s.servicePrice).toFixed(2)} ({s.serviceDuration} min)
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -834,6 +879,12 @@ function AppointmentsPage() {
|
||||
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 (
|
||||
<>
|
||||
<input
|
||||
@@ -846,61 +897,107 @@ function AppointmentsPage() {
|
||||
{filteredActive.length === 0 ? (
|
||||
<p className="text-[#888] text-[0.9rem] py-4 m-0">{activeAppts.length === 0 ? "No active appointments." : "No results."}</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{filteredActive.map((a) => (
|
||||
<div key={a.appointmentId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.serviceName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.appointmentStatus?.toLowerCase()}`}>
|
||||
{a.appointmentStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] mb-2 flex-wrap">
|
||||
<span>{a.storeName}</span>
|
||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||
</div>
|
||||
{a.petName && (
|
||||
<div className="text-[0.85rem] text-[#888] mb-2">Pet: {a.petName}</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 rounded-lg border border-[#f5c6c6] bg-[#fff0f0] text-[#c0392b] text-[0.85rem] cursor-pointer hover:bg-[#ffd7d7] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={cancellingId === a.appointmentId}
|
||||
onClick={() => handleCancelAppointment(a.appointmentId)}
|
||||
>
|
||||
{cancellingId === a.appointmentId ? "Cancelling..." : "Cancel"}
|
||||
</button>
|
||||
<>
|
||||
<div className="flex flex-col gap-3">
|
||||
{apptSlice.map((a) => (
|
||||
<div key={a.appointmentId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.serviceName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.appointmentStatus?.toLowerCase()}`}>
|
||||
{a.appointmentStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] mb-2 flex-wrap">
|
||||
<span>{a.storeName}</span>
|
||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||
</div>
|
||||
{a.petName && (
|
||||
<div className="text-[0.85rem] text-[#888] mb-2">Pet: {a.petName}</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 rounded-lg border border-[#f5c6c6] bg-[#fff0f0] text-[#c0392b] text-[0.85rem] cursor-pointer hover:bg-[#ffd7d7] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={cancellingId === a.appointmentId}
|
||||
onClick={() => handleCancelAppointment(a.appointmentId)}
|
||||
>
|
||||
{cancellingId === a.appointmentId ? "Cancelling..." : "Cancel"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{apptTotalPages > 1 && (
|
||||
<div className="flex items-center justify-between gap-2 mt-1 flex-wrap">
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setApptPage((p) => p - 1)}
|
||||
disabled={apptPage === 0}
|
||||
type="button"
|
||||
>
|
||||
← Prev
|
||||
</button>
|
||||
<span className="text-[0.82rem] text-[#888]">Page {apptPage + 1} of {apptTotalPages}</span>
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setApptPage((p) => p + 1)}
|
||||
disabled={apptPage >= apptTotalPages - 1}
|
||||
type="button"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{pastAppts.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<button className="text-[0.85rem] text-[#e68672] cursor-pointer bg-transparent border-none font-semibold hover:underline" onClick={() => setShowPastAppts((v) => !v)}>
|
||||
<button className="text-[0.85rem] text-[#e68672] cursor-pointer bg-transparent border-none font-semibold hover:underline" onClick={() => { setShowPastAppts((v) => !v); setPastApptPage(0); }}>
|
||||
{showPastAppts ? "Hide" : "Show"} past appointments ({pastAppts.length})
|
||||
</button>
|
||||
{showPastAppts && (
|
||||
<div className="flex flex-col gap-3 mt-3 opacity-75">
|
||||
{pastAppts.map((a) => (
|
||||
<div key={a.appointmentId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.serviceName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.appointmentStatus?.toLowerCase()}`}>
|
||||
{a.appointmentStatus}
|
||||
</span>
|
||||
<>
|
||||
<div className="flex flex-col gap-3 mt-3 opacity-75">
|
||||
{pastApptSlice.map((a) => (
|
||||
<div key={a.appointmentId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.serviceName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.appointmentStatus?.toLowerCase()}`}>
|
||||
{a.appointmentStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] flex-wrap">
|
||||
<span>{a.storeName}</span>
|
||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||
</div>
|
||||
{a.petName && (
|
||||
<div className="text-[0.85rem] text-[#888] mt-1">Pet: {a.petName}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] flex-wrap">
|
||||
<span>{a.storeName}</span>
|
||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||
</div>
|
||||
{a.petName && (
|
||||
<div className="text-[0.85rem] text-[#888] mt-1">Pet: {a.petName}</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
{pastApptTotalPages > 1 && (
|
||||
<div className="flex items-center justify-between gap-2 mt-2 flex-wrap">
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setPastApptPage((p) => p - 1)}
|
||||
disabled={pastApptPage === 0}
|
||||
type="button"
|
||||
>
|
||||
← Prev
|
||||
</button>
|
||||
<span className="text-[0.82rem] text-[#888]">Page {pastApptPage + 1} of {pastApptTotalPages}</span>
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setPastApptPage((p) => p + 1)}
|
||||
disabled={pastApptPage >= pastApptTotalPages - 1}
|
||||
type="button"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -918,6 +1015,12 @@ function AppointmentsPage() {
|
||||
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 (
|
||||
<>
|
||||
<input
|
||||
@@ -930,55 +1033,101 @@ function AppointmentsPage() {
|
||||
{filteredActive.length === 0 ? (
|
||||
<p className="text-[#888] text-[0.9rem] py-4 m-0">{activeAdoptions.length === 0 ? "No active adoption requests." : "No results."}</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{filteredActive.map((a) => (
|
||||
<div key={a.adoptionId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.petName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.adoptionStatus?.toLowerCase()}`}>
|
||||
{a.adoptionStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] mb-2 flex-wrap">
|
||||
<span>{a.sourceStoreName}</span>
|
||||
<span>{a.adoptionDate}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 rounded-lg border border-[#f5c6c6] bg-[#fff0f0] text-[#c0392b] text-[0.85rem] cursor-pointer hover:bg-[#ffd7d7] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={cancellingId === a.adoptionId}
|
||||
onClick={() => handleCancelAdoption(a.adoptionId)}
|
||||
>
|
||||
{cancellingId === a.adoptionId ? "Cancelling..." : "Cancel"}
|
||||
</button>
|
||||
<>
|
||||
<div className="flex flex-col gap-3">
|
||||
{adoptionSlice.map((a) => (
|
||||
<div key={a.adoptionId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.petName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.adoptionStatus?.toLowerCase()}`}>
|
||||
{a.adoptionStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] mb-2 flex-wrap">
|
||||
<span>{a.sourceStoreName}</span>
|
||||
<span>{a.adoptionDate}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 rounded-lg border border-[#f5c6c6] bg-[#fff0f0] text-[#c0392b] text-[0.85rem] cursor-pointer hover:bg-[#ffd7d7] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={cancellingId === a.adoptionId}
|
||||
onClick={() => handleCancelAdoption(a.adoptionId)}
|
||||
>
|
||||
{cancellingId === a.adoptionId ? "Cancelling..." : "Cancel"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{adoptionTotalPages > 1 && (
|
||||
<div className="flex items-center justify-between gap-2 mt-1 flex-wrap">
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setAdoptionPage((p) => p - 1)}
|
||||
disabled={adoptionPage === 0}
|
||||
type="button"
|
||||
>
|
||||
← Prev
|
||||
</button>
|
||||
<span className="text-[0.82rem] text-[#888]">Page {adoptionPage + 1} of {adoptionTotalPages}</span>
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setAdoptionPage((p) => p + 1)}
|
||||
disabled={adoptionPage >= adoptionTotalPages - 1}
|
||||
type="button"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{pastAdoptions.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<button className="text-[0.85rem] text-[#e68672] cursor-pointer bg-transparent border-none font-semibold hover:underline" onClick={() => setShowPastAdoptions((v) => !v)}>
|
||||
<button className="text-[0.85rem] text-[#e68672] cursor-pointer bg-transparent border-none font-semibold hover:underline" onClick={() => { setShowPastAdoptions((v) => !v); setPastAdoptionPage(0); }}>
|
||||
{showPastAdoptions ? "Hide" : "Show"} past adoptions ({pastAdoptions.length})
|
||||
</button>
|
||||
{showPastAdoptions && (
|
||||
<div className="flex flex-col gap-3 mt-3 opacity-75">
|
||||
{pastAdoptions.map((a) => (
|
||||
<div key={a.adoptionId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.petName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.adoptionStatus?.toLowerCase()}`}>
|
||||
{a.adoptionStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] flex-wrap">
|
||||
<span>{a.sourceStoreName}</span>
|
||||
<span>{a.adoptionDate}</span>
|
||||
<>
|
||||
<div className="flex flex-col gap-3 mt-3 opacity-75">
|
||||
{pastAdoptionSlice.map((a) => (
|
||||
<div key={a.adoptionId} className="bg-[#f9f9f9] rounded-xl p-4 border border-[#eee]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-semibold text-[#333]">{a.petName}</span>
|
||||
<span className={`text-xs font-semibold rounded-full px-2.5 py-1 appt-card-status--${a.adoptionStatus?.toLowerCase()}`}>
|
||||
{a.adoptionStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 text-[0.85rem] text-[#666] flex-wrap">
|
||||
<span>{a.sourceStoreName}</span>
|
||||
<span>{a.adoptionDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{pastAdoptionTotalPages > 1 && (
|
||||
<div className="flex items-center justify-between gap-2 mt-2 flex-wrap">
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setPastAdoptionPage((p) => p - 1)}
|
||||
disabled={pastAdoptionPage === 0}
|
||||
type="button"
|
||||
>
|
||||
← Prev
|
||||
</button>
|
||||
<span className="text-[0.82rem] text-[#888]">Page {pastAdoptionPage + 1} of {pastAdoptionTotalPages}</span>
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-lg border border-[#ddd] bg-white text-[0.85rem] cursor-pointer hover:border-[#e68672] transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => setPastAdoptionPage((p) => p + 1)}
|
||||
disabled={pastAdoptionPage >= pastAdoptionTotalPages - 1}
|
||||
type="button"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user