diff --git a/src/App.tsx b/src/App.tsx index d791615..06ca32f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,9 @@ import { AuthProvider } from "./context/AuthContext"; import { Toaster } from "react-hot-toast"; // <-- import do Toaster import type { JSX } from "react"; import StaffList from "./pages/Staff/StaffList.tsx"; +import AdopterRegister from "./pages/Adopter/AdopterRegister.tsx"; +import AdopterList from "./pages/Adopter/AdopterList.tsx"; + const ProtectedLayout = ({ children }: { children: JSX.Element }) => (
@@ -89,6 +92,26 @@ export default function App() { } /> + + + + + + } + /> + + + + + + } + /> ( + message ? {message} : null +); + +export default function AdopterList() { + const [adopters, setAdopters] = useState([]); + const [selectedAdopter, setSelectedAdopter] = useState(null); + const [editing, setEditing] = useState(null); + const [deleteAdopter, setDeleteAdopter] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [errors, setErrors] = useState({}); + const navigate = useNavigate(); + + useEffect(() => { + const fetchData = async () => { + try { + const data = await getAdopter(); + setAdopters(data); + } catch (err) { + console.error(err); + toast.error("Erro ao carregar adotantes."); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + + const filteredAdopters = adopters.filter(a => + [a.name, a.email, a.phone, a.cpf, a.rg].some(field => + field?.toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + + const inputStyle = (hasError?: boolean) => + `w-full px-4 py-3 rounded-xl shadow-sm focus:ring-2 focus:outline-none transition placeholder-gray-400 text-gray-800 bg-gray-100/70 ${hasError ? "ring-red-500 focus:ring-red-500" : "focus:ring-blue-500"}`; + + const handleEditChange = ( + e: ChangeEvent + ) => { + if (!editing) return; + + const { name, value, type } = e.target; + let newValue: string | boolean = value; + + if (name === "cpf") newValue = formatCPF(value); + if (name === "cep") newValue = formatCEP(value); + if (name === "phone") newValue = formatPhone(value); + + if (type === "checkbox") newValue = (e.target as HTMLInputElement).checked; + if (name === "hasPets") newValue = value === "true"; + + setEditing({ ...editing, [name]: newValue }); + }; + + const validateEdit = () => { + if (!editing) return false; + + const otherAdopters = adopters.filter(a => a.id !== editing.id); + const newErrors: Errors = {}; + + if (!editing.name || editing.name.trim() === "") newErrors.name = "Campo obrigatório"; + if (!editing.email || editing.email.trim() === "") newErrors.email = "Campo obrigatório"; + else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(editing.email)) newErrors.email = "Email inválido"; + else if (otherAdopters.some(a => a.email === editing.email)) newErrors.email = "Email já cadastrado"; + + if (!editing.phone || editing.phone.trim() === "") newErrors.phone = "Campo obrigatório"; + else if (!/^\(?\d{2}\)?\s?\d{4,5}-?\d{4}$/.test(editing.phone)) newErrors.phone = "Telefone inválido"; + else if (otherAdopters.some(a => a.phone === editing.phone)) newErrors.phone = "Telefone já cadastrado"; + + if (!editing.cpf || editing.cpf.trim() === "") newErrors.cpf = "Campo obrigatório"; + else { + const cpfNumbers = editing.cpf.replace(/\D/g, ""); + if (!/^\d{11}$/.test(cpfNumbers)) newErrors.cpf = "CPF inválido"; + else if (otherAdopters.some(a => a.cpf === editing.cpf)) newErrors.cpf = "CPF já cadastrado"; + } + + if (!editing.rg || editing.rg.trim() === "") newErrors.rg = "Campo obrigatório"; + else if (otherAdopters.some(a => a.rg === editing.rg)) newErrors.rg = "RG já cadastrado"; + + if (!editing.sex || editing.sex === "") newErrors.sex = "Campo obrigatório"; + + if (editing.hasPets === undefined || editing.hasPets === null) newErrors.hasPets = "Campo obrigatório"; + + if (!editing.state || editing.state === "") newErrors.state = "Campo obrigatório"; + if (!editing.city || editing.city.trim() === "") newErrors.city = "Campo obrigatório"; + if (!editing.district || editing.district.trim() === "") newErrors.district = "Campo obrigatório"; + if (!editing.street || editing.street.trim() === "") newErrors.street = "Campo obrigatório"; + if (!editing.number || editing.number.trim() === "") newErrors.number = "Campo obrigatório"; + if (!editing.cep || editing.cep.trim() === "") newErrors.cep = "Campo obrigatório"; + + setErrors(newErrors); + + if (Object.keys(newErrors).length > 0) { + toast.error("Corrija os campos destacados."); + return false; + } + + return true; + }; + + const handleEditSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!editing) return; + if (!validateEdit()) return; + + setSaving(true); + await toast.promise( + updateAdopterById(editing.id, editing), + { + loading: "Salvando...", + success: "Adotante atualizado com sucesso!", + error: "Erro ao atualizar adotante." + } + ); + setAdopters(prev => prev.map(a => (a.id === editing.id ? editing : a))); + setEditing(null); + setSaving(false); + }; + + const confirmDelete = async () => { + if (!deleteAdopter) return; + await toast.promise( + deleteAdopterById(deleteAdopter.id), + { + loading: "Excluindo...", + success: "Adotante removido com sucesso!", + error: "Erro ao excluir adotante." + } + ); + setAdopters(prev => prev.filter(a => a.id !== deleteAdopter.id)); + setDeleteAdopter(null); + }; + + return ( +
+ {/* Header */} +
+

Adotantes

+
+ setSearchTerm(e.target.value)} + className="flex-1 sm:w-64 px-4 py-2 border border-gray-300 rounded-xl shadow-inner focus:outline-none focus:ring-2 focus:ring-blue-500 transition" + /> + +
+
+ + {/* Grid de adotantes */} + {loading ? ( +
+ {[...Array(6)].map((_, i) => ( +
+ ))} +
+ ) : filteredAdopters.length === 0 ? ( +
Nenhum adotante encontrado.
+ ) : ( +
+ {filteredAdopters.map(a => ( +
+
+ {a.sex === "Feminino" ? ( + Avatar feminino + ) : a.sex === "Masculino" ? ( + Avatar masculino + ) : ( + Avatar outros + )} + + +
+
+

{a.name}

+

{a.email}

+

{a.phone}

+

{a.sex || "Não informado"}

+ +
+
+ ))} +
+ )} + + + {/* Modal detalhes */} + {selectedAdopter && ( + + + +

{selectedAdopter.name}

+
+

Email: {selectedAdopter.email}

+

Telefone: {selectedAdopter.phone}

+

Sexo: {selectedAdopter.sex || "Não informado"}

+

RG: {selectedAdopter.rg || "Não informado"}

+

CPF: {selectedAdopter.cpf || "Não informado"}

+

Endereço: {`${selectedAdopter.street || ""} ${selectedAdopter.number || ""}, ${selectedAdopter.district || ""}, ${selectedAdopter.city || ""} - ${selectedAdopter.state || ""}`}

+

CEP: {selectedAdopter.cep || "Não informado"}

+

Estado Civil: {selectedAdopter.maritalStatus || "Não informado"}

+

Possui pets: {selectedAdopter.hasPets ? "Sim" : "Não"}

+

Observações: {selectedAdopter.notes || "Nenhuma"}

+
+
+ + +
+
+
+ )} + + {/* Modal edição */} + {editing && ( + + + +

Editar Adotante

+
+
+ {/* Inputs */} +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ {/* Estado Civil */} +
+ + + +
+ {/* Estado */} +
+ + + +
+ {/* Cidade */} +
+ + + +
+ {/* Bairro */} +
+ + + +
+ {/* Rua */} +
+ + + +
+ + {/* Número */} +
+ + + +
+ + {/* Complemento */} +
+ + + +
+ + {/* CEP */} +
+ + + +
+ +
+ + + +
+ +
+ +
+ + + +
+
+ + {/* Observações */} +
+ +