diff --git a/src/Components/ProtectedSkeleton/AnimalListSkeleton.tsx b/src/Components/ProtectedSkeleton/AnimalListSkeleton.tsx new file mode 100644 index 0000000..f89193e --- /dev/null +++ b/src/Components/ProtectedSkeleton/AnimalListSkeleton.tsx @@ -0,0 +1,30 @@ +interface Props { + count?: number; +} + +export default function AnimalListSkeleton({ count = 8 }: Props) { + const items = Array.from({ length: count }); + + return ( +
+ {items.map((_, idx) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/context/AnimalsContext.tsx b/src/context/AnimalsContext.tsx index dd6f9aa..ff28868 100644 --- a/src/context/AnimalsContext.tsx +++ b/src/context/AnimalsContext.tsx @@ -1,5 +1,5 @@ import { createContext, useContext, useState, useEffect, type ReactNode } from "react"; -import { collection, onSnapshot, doc, updateDoc } from "firebase/firestore"; +import { collection, onSnapshot, doc, updateDoc, getDocs } from "firebase/firestore"; import { db } from "../lib/firebase.ts"; export type Animal = { @@ -22,6 +22,8 @@ export type Animal = { type AnimalsContextType = { animals: Animal[]; + loading: boolean; + refresh: () => Promise; updateAnimalStatus: (id: string, status: Animal["status"]) => Promise; removeAnimalFromContext: (id: string) => void; markAnimalAsAdopted: (id: string) => Promise; @@ -32,25 +34,51 @@ const AnimalsContext = createContext(undefined); export const AnimalsProvider = ({ children }: { children: ReactNode }) => { const [animals, setAnimals] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { const animalsCol = collection(db, "animals"); + setLoading(true); - const unsubscribe = onSnapshot(animalsCol, (snapshot) => { - const formatted = snapshot.docs.map((doc) => ({ + const unsubscribe = onSnapshot( + animalsCol, + (snapshot) => { + const formatted = snapshot.docs.map((doc) => ({ + id: doc.id, + ...(doc.data() as Omit), + })); + setAnimals(formatted); + setLoading(false); + }, + (error) => { + console.error("Erro no snapshot de animals:", error); + setLoading(false); + } + ); + + return () => unsubscribe(); + }, []); + + const refresh = async () => { + setLoading(true); + try { + const animalsCol = collection(db, "animals"); + const snap = await getDocs(animalsCol); + const formatted = snap.docs.map((doc) => ({ id: doc.id, ...(doc.data() as Omit), })); setAnimals(formatted); - }); - - return () => unsubscribe(); - }, []); + } catch (err) { + console.error("Erro ao atualizar animais:", err); + } finally { + setLoading(false); + } + }; const updateAnimalStatus = async (id: string, status: Animal["status"]) => { const docRef = doc(db, "animals", id); await updateDoc(docRef, { status }); - setAnimals(prev => prev.map(a => a.id === id ? { ...a, status } : a)); }; @@ -69,6 +97,8 @@ export const AnimalsProvider = ({ children }: { children: ReactNode }) => { return ( (null); @@ -14,8 +16,11 @@ export default function AnimalList() { const [searchTerm, setSearchTerm] = useState(""); const [selectedAnimal, setSelectedAnimal] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 8; + const navigate = useNavigate(); - const { animals, updateAnimalStatus, removeAnimalFromContext } = useAnimals(); + const { animals, loading, updateAnimalStatus, removeAnimalFromContext } = useAnimals(); const filteredAnimals = animals.filter( a => @@ -23,6 +28,19 @@ export default function AnimalList() { a.breed?.toLowerCase().includes(searchTerm.toLowerCase()) ); + const totalPages = Math.max(1, Math.ceil(filteredAnimals.length / pageSize)); + useEffect(() => { + if (currentPage > totalPages) setCurrentPage(totalPages); + }, [totalPages, currentPage]); + + useEffect(() => { + setCurrentPage(1); + }, [searchTerm]); + + const startIndex = (currentPage - 1) * pageSize; + const endIndex = Math.min(startIndex + pageSize, filteredAnimals.length); + const paginatedAnimals = filteredAnimals.slice(startIndex, endIndex); + const getStatusStyle = (status: Animal["status"]) => { switch (status) { case "Disponível": @@ -107,6 +125,15 @@ export default function AnimalList() { } }; + const getPageNumbers = () => { + const pages: number[] = []; + let start = Math.max(1, currentPage - 2); + const end = Math.min(totalPages, start + 4); + if (end - start < 4) start = Math.max(1, end - 4); + for (let i = start; i <= end; i++) pages.push(i); + return pages; + }; + return (
@@ -127,55 +154,96 @@ export default function AnimalList() {
- {filteredAnimals.length === 0 ? ( + {loading ? ( + + ) : filteredAnimals.length === 0 ? (
Nenhum animal encontrado.
) : ( -
- {filteredAnimals.map(animal => ( -
-
- Imagem - {animal.image && ( - {animal.name} - )} - -
-
-
-

{animal.name}

- +
+ {paginatedAnimals.map(animal => ( +
+
+ Imagem + {animal.image && ( + {animal.name} + )} +
-

- {calcularIdade(animal.birthDate)} • {animal.breed || "-"} -

+
+
+

{animal.name}

+ + {animal.status} + +
+

+ {calcularIdade(animal.birthDate)} • {animal.breed || "-"} +

+ +
+
+ ))} +
+ +
+
+ + + {getPageNumbers().map(n => ( -
+ ))} + +
- ))} -
+ +
+ Mostrando {startIndex + 1}–{endIndex} de {filteredAnimals.length} +
+
+ )} @@ -429,4 +497,4 @@ export default function AnimalList() { )}
); -} +} \ No newline at end of file diff --git a/src/pages/Animal/AnimalRegister.tsx b/src/pages/Animal/AnimalRegister.tsx index a373e33..7d7e251 100644 --- a/src/pages/Animal/AnimalRegister.tsx +++ b/src/pages/Animal/AnimalRegister.tsx @@ -162,12 +162,24 @@ export default function AnimalRegister() {
{errors.sex && {errors.sex}}