diff --git a/package-lock.json b/package-lock.json
index 9fb1714..5fa2586 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -923,6 +923,7 @@
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.2.tgz",
"integrity": "sha512-Ecx2ig/JLC9ayIQwZHqm41Tzlf4c1WUuFhFUZB1y+JIJqDRE579x7Uil7tKT8MwDpOPwrK5ZtpxdSsrfy/LF8Q==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@firebase/component": "0.7.0",
"@firebase/logger": "0.5.0",
@@ -989,6 +990,7 @@
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.2.tgz",
"integrity": "sha512-cn+U27GDaBS/irsbvrfnPZdcCzeZPRGKieSlyb7vV6LSOL6mdECnB86PgYjYGxSNg8+U48L/NeevTV1odU+mOQ==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@firebase/app": "0.14.2",
"@firebase/component": "0.7.0",
@@ -1004,7 +1006,8 @@
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
"integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
- "license": "Apache-2.0"
+ "license": "Apache-2.0",
+ "peer": true
},
"node_modules/@firebase/auth": {
"version": "1.11.0",
@@ -1455,6 +1458,7 @@
"integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"tslib": "^2.1.0"
},
@@ -2656,6 +2660,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -2731,6 +2736,7 @@
"integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.41.0",
"@typescript-eslint/types": "8.41.0",
@@ -2966,6 +2972,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3219,7 +3226,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/d3-array": {
"version": "3.2.4",
@@ -3497,6 +3505,7 @@
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -4908,6 +4917,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4917,6 +4927,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -4962,6 +4973,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -5086,7 +5098,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -5451,6 +5464,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -5509,6 +5523,7 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5607,6 +5622,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -5698,6 +5714,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -5795,20 +5812,6 @@
"node": ">=18"
}
},
- "node_modules/yaml": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
- "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
- "license": "ISC",
- "optional": true,
- "peer": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- }
- },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/src/App.tsx b/src/App.tsx
index 68ef389..b40cef7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -18,6 +18,9 @@ import type {JSX} from "react";
import {AnimalsProvider} from "./context/AnimalsContext.tsx";
import { AdoptionsProvider } from "./context/AdoptionsContext";
import VaccineRegister from "./pages/Health/Vaccine/VaccineRegister.tsx";
+import DewormingRegister from "./pages/Health/DewormingRegister/DewormingRegister.tsx";
+import AntiparasiticRegister from "./pages/Health/AntiparasiticRegister/AntiparasiticRegister.tsx";
+import HealthCardList from "./pages/Health/HealthCardList/HealthCardList.tsx";
const ProtectedLayout = ({children}: { children: JSX.Element }) => (
@@ -57,6 +60,9 @@ export default function App() {
}/>}/>
}/>}/>
+ }/>}/>
+ }/>}/>
+ }/>}/>
}/>}/>
diff --git a/src/pages/Health/AntiparasiticRegister/AntiparasiticRegister.tsx b/src/pages/Health/AntiparasiticRegister/AntiparasiticRegister.tsx
new file mode 100644
index 0000000..6c3e4c7
--- /dev/null
+++ b/src/pages/Health/AntiparasiticRegister/AntiparasiticRegister.tsx
@@ -0,0 +1,369 @@
+import { useEffect, useState } from "react";
+import { motion } from "framer-motion";
+import { Bug, Save } from "lucide-react";
+import { useNavigate, Link } from "react-router-dom";
+import toast from "react-hot-toast";
+import Select from "react-select";
+import { FiArrowLeft } from "react-icons/fi";
+import { useAnimals } from "../../../context/AnimalsContext.tsx";
+import { getStaff } from "../../../services/staffService";
+import { registerAntiparasitic } from "../../../services/antiparasiticService.ts";
+
+export default function AntiparasiticRegister() {
+ const [loading, setLoading] = useState(false);
+ const navigate = useNavigate();
+ const { animals } = useAnimals();
+
+ const [formData, setFormData] = useState({
+ antiparasiticId: [] as string[],
+ animalId: "",
+ employeeId: "",
+ applicationDate: "",
+ nextApplicationDate: "",
+ notes: "",
+ });
+
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+ const [employees, setEmployees] = useState([]);
+ const [selectedAntiparasitic, setSelectedAntiparasitic] = useState([]);
+ const [selectedAnimal, setSelectedAnimal] = useState(null);
+ const [selectedEmployee, setSelectedEmployee] = useState(null);
+
+ const antiparasiticOptions = [
+ {
+ label: "Cães",
+ options: [
+ { value: "antipulgas_bravecto", label: "Bravecto" },
+ { value: "antipulgas_nexgard", label: "NexGard" },
+ { value: "antipulgas_simparic", label: "Simparic" },
+ { value: "antipulgas_advocate", label: "Advocate" },
+ ],
+ },
+ {
+ label: "Gatos",
+ options: [
+ { value: "antipulgas_revolution", label: "Revolution" },
+ { value: "antipulgas_frontline", label: "Frontline" },
+ { value: "antipulgas_advantage", label: "Advantage" },
+ { value: "antipulgas_profender", label: "Profender" },
+ ],
+ },
+ ];
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const staffData = await getStaff();
+ setEmployees(staffData);
+ } catch (error) {
+ console.error(error);
+ toast.error("Erro ao carregar dados.");
+ }
+ };
+ fetchData();
+ }, []);
+
+ const animalOptions = animals.map((a: { id: any; name: any }) => ({
+ value: a.id,
+ label: a.name,
+ }));
+
+ const employeeOptions = employees.map((e) => ({
+ value: e.id,
+ label: e.name,
+ }));
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData({ ...formData, [name]: value });
+ setErrors({ ...errors, [name]: "" });
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+ const requiredFields = [
+ "antiparasiticId",
+ "animalId",
+ "employeeId",
+ "applicationDate",
+ "nextApplicationDate",
+ ];
+
+ requiredFields.forEach((field) => {
+ if (
+ !formData[field as keyof typeof formData] ||
+ (Array.isArray(formData[field as keyof typeof formData]) &&
+ formData[field as keyof typeof formData].length === 0)
+ ) {
+ newErrors[field] = "Campo obrigatório";
+ }
+ });
+
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!validate()) return;
+
+ setLoading(true);
+ try {
+ await registerAntiparasitic(formData);
+ toast.success("Antiparasitário registrado com sucesso!");
+
+ setFormData({
+ antiparasiticId: [],
+ animalId: "",
+ employeeId: "",
+ applicationDate: "",
+ nextApplicationDate: "",
+ notes: "",
+ });
+ setSelectedAntiparasitic([]);
+ setSelectedAnimal(null);
+ setSelectedEmployee(null);
+ setErrors({});
+ } catch (error) {
+ console.error(error);
+ toast.error("Erro ao registrar antiparasitário.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const inputModern = (error?: boolean) =>
+ `w-full px-4 py-3 rounded-xl shadow-sm focus:ring-2 focus:outline-none transition placeholder-gray-400 text-gray-800 ${
+ error
+ ? "border-2 border-red-500 focus:ring-red-500 bg-red-50"
+ : "bg-gray-100/70 focus:ring-blue-500"
+ }`;
+
+ const errorStyle = "text-sm text-red-500 mt-1";
+
+ const selectStyles = (error?: boolean) => ({
+ control: (base: any) => ({
+ ...base,
+ borderRadius: "0.75rem",
+ padding: "2px 4px",
+ backgroundColor: "#f3f4f6",
+ borderColor: error ? "#ef4444" : "#d1d5db",
+ boxShadow: "none",
+ "&:hover": { borderColor: "#3b82f6" },
+ minHeight: "50px",
+ }),
+ valueContainer: (base: any) => ({
+ ...base,
+ maxHeight: "80px",
+ overflowY: "auto",
+ }),
+ multiValue: (base: any) => ({
+ ...base,
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ }),
+ multiValueLabel: (base: any) => ({
+ ...base,
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ }),
+ menu: (base: any) => ({
+ ...base,
+ borderRadius: "0.75rem",
+ zIndex: 20,
+ }),
+ });
+
+ return (
+
+
+ Registro de Antiparasitário
+
+
+
+
+
+ Voltar
+
+
+ Informações do Antiparasitário
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {errors.applicationDate && (
+
+ {errors.applicationDate}
+
+ )}
+
+
+
+
+
+ {errors.nextApplicationDate && (
+
+ {errors.nextApplicationDate}
+
+ )}
+
+
+
+
+
+
+
+ navigate("/health-card")}
+ className="w-full sm:w-auto px-6 py-3 rounded-2xl bg-gray-600 text-white shadow hover:bg-gray-700 transition font-medium"
+ >
+ Carteiras de Saúde
+
+
+
+ {loading ? (
+ "Salvando..."
+ ) : (
+ <>
+ Salvar
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/src/pages/Health/DewormingRegister/DewormingRegister.tsx b/src/pages/Health/DewormingRegister/DewormingRegister.tsx
new file mode 100644
index 0000000..2277b14
--- /dev/null
+++ b/src/pages/Health/DewormingRegister/DewormingRegister.tsx
@@ -0,0 +1,352 @@
+import { useEffect, useState } from "react";
+import { motion } from "framer-motion";
+import { Pill, Save } from "lucide-react";
+import { useNavigate, Link } from "react-router-dom";
+import toast from "react-hot-toast";
+import Select from "react-select";
+import { FiArrowLeft } from "react-icons/fi";
+import { useAnimals } from "../../../context/AnimalsContext.tsx";
+import { getStaff } from "../../../services/staffService";
+import { registerDeworming } from "../../../services/dewormingService.ts";
+
+export default function DewormingRegister() {
+ const [loading, setLoading] = useState(false);
+ const navigate = useNavigate();
+ const { animals } = useAnimals();
+
+ const [formData, setFormData] = useState({
+ dewormerId: [] as string[],
+ animalId: "",
+ employeeId: "",
+ applicationDate: "",
+ nextApplicationDate: "",
+ notes: "",
+ });
+
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+ const [employees, setEmployees] = useState([]);
+ const [selectedDewormer, setSelectedDewormer] = useState([]);
+ const [selectedAnimal, setSelectedAnimal] = useState(null);
+ const [selectedEmployee, setSelectedEmployee] = useState(null);
+
+ const dewormerOptions = [
+ {
+ label: "Cães",
+ options: [
+ { value: "vermifugo_drontal_plus", label: "Drontal Plus" },
+ { value: "vermifugo_endogard", label: "Endogard" },
+ { value: "vermifugo_canex", label: "Canex" },
+ { value: "vermifugo_panthol", label: "Panthol" },
+ ],
+ },
+ {
+ label: "Gatos",
+ options: [
+ { value: "vermifugo_profender", label: "Profender" },
+ { value: "vermifugo_milbemax", label: "Milbemax" },
+ { value: "vermifugo_helmix", label: "Helmix" },
+ { value: "vermifugo_endogard_gato", label: "Endogard Gato" },
+ ],
+ },
+ ];
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const staffData = await getStaff();
+ setEmployees(staffData);
+ } catch (error) {
+ console.error(error);
+ toast.error("Erro ao carregar dados.");
+ }
+ };
+ fetchData();
+ }, []);
+
+ const animalOptions = animals.map((a: { id: any; name: any }) => ({
+ value: a.id,
+ label: a.name,
+ }));
+ const employeeOptions = employees.map((e) => ({
+ value: e.id,
+ label: e.name,
+ }));
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData({ ...formData, [name]: value });
+ setErrors({ ...errors, [name]: "" });
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+ const requiredFields = [
+ "dewormerId",
+ "animalId",
+ "employeeId",
+ "applicationDate",
+ "nextApplicationDate",
+ ];
+ requiredFields.forEach((field) => {
+ if (
+ !formData[field as keyof typeof formData] ||
+ (Array.isArray(formData[field as keyof typeof formData]) &&
+ formData[field as keyof typeof formData].length === 0)
+ ) {
+ newErrors[field] = "Campo obrigatório";
+ }
+ });
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!validate()) return;
+
+ setLoading(true);
+ try {
+ await registerDeworming(formData);
+ toast.success("Vermifugação registrada com sucesso!");
+
+ setFormData({
+ dewormerId: [],
+ animalId: "",
+ employeeId: "",
+ applicationDate: "",
+ nextApplicationDate: "",
+ notes: "",
+ });
+ setSelectedDewormer([]);
+ setSelectedAnimal(null);
+ setSelectedEmployee(null);
+ setErrors({});
+ } catch (error) {
+ console.error(error);
+ toast.error("Erro ao registrar vermifugação.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const inputModern = (error?: boolean) =>
+ `w-full px-4 py-3 rounded-xl shadow-sm focus:ring-2 focus:outline-none transition placeholder-gray-400 text-gray-800 ${
+ error
+ ? "border-2 border-red-500 focus:ring-red-500 bg-red-50"
+ : "bg-gray-100/70 focus:ring-blue-500"
+ }`;
+
+ const errorStyle = "text-sm text-red-500 mt-1";
+
+ const selectStyles = (error?: boolean) => ({
+ control: (base: any) => ({
+ ...base,
+ borderRadius: "0.75rem",
+ padding: "2px 4px",
+ backgroundColor: "#f3f4f6",
+ borderColor: error ? "#ef4444" : "#d1d5db",
+ boxShadow: "none",
+ "&:hover": { borderColor: "#3b82f6" },
+ minHeight: "50px",
+ }),
+ valueContainer: (base: any) => ({
+ ...base,
+ maxHeight: "80px",
+ overflowY: "auto",
+ }),
+ multiValue: (base: any) => ({
+ ...base,
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ }),
+ menu: (base: any) => ({
+ ...base,
+ borderRadius: "0.75rem",
+ zIndex: 20,
+ }),
+ });
+
+ return (
+
+
+ Registro de Vermifugação
+
+
+
+
+
+ Voltar
+
+
+ Informações do Vermífugo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {errors.applicationDate && (
+ {errors.applicationDate}
+ )}
+
+
+
+
+
+ {errors.nextApplicationDate && (
+ {errors.nextApplicationDate}
+ )}
+
+
+
+
+
+
+
+ navigate("/health-card")}
+ className="w-full sm:w-auto px-6 py-3 rounded-2xl bg-gray-600 text-white shadow hover:bg-gray-700 transition font-medium"
+ >
+ Carteiras de Saúde
+
+
+
+ {loading ? (
+ "Salvando..."
+ ) : (
+ <>
+ Salvar
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/src/pages/Health/HealthCardList/HealthCardList.tsx b/src/pages/Health/HealthCardList/HealthCardList.tsx
new file mode 100644
index 0000000..fa87ef2
--- /dev/null
+++ b/src/pages/Health/HealthCardList/HealthCardList.tsx
@@ -0,0 +1,325 @@
+import { useEffect, useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import { Syringe, Shield, Pill, X, Trash2 } from "lucide-react";
+import { useAnimals } from "../../../context/AnimalsContext.tsx";
+import { getVaccines, deleteVaccineRecord } from "../../../services/vaccineService.ts";
+import { getAntiparasitics, deleteAntiparasiticRecord } from "../../../services/antiparasiticService.ts";
+import { getDewormings, deleteDewormingRecord } from "../../../services/dewormingService.ts";
+import toast from "react-hot-toast";
+import type {HealthRecord} from "../types/healthRecord.ts";
+
+export default function HealthCardList() {
+ const { animals } = useAnimals();
+ const [searchTerm, setSearchTerm] = useState("");
+ const [selectedAnimal, setSelectedAnimal] = useState(null);
+ const [animalHealth, setAnimalHealth] = useState<{
+ vaccines: HealthRecord[];
+ antiparasitics: HealthRecord[];
+ dewormings: HealthRecord[];
+ }>({
+ vaccines: [],
+ antiparasitics: [],
+ dewormings: [],
+ });
+
+ useEffect(() => {
+ const fetchHealthData = async () => {
+ try {
+ const [vaccineData, antiparasiticData, dewormingData] = await Promise.all([
+ getVaccines(),
+ getAntiparasitics(),
+ getDewormings(),
+ ]);
+ setAnimalHealth({
+ vaccines: vaccineData,
+ antiparasitics: antiparasiticData,
+ dewormings: dewormingData,
+ });
+ } catch (err) {
+ console.error(err);
+ toast.error("Erro ao carregar dados de saúde.");
+ }
+ };
+ fetchHealthData();
+ }, []);
+
+ const filteredAnimals = animals.filter(
+ (a) =>
+ a.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ a.breed?.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const calcularIdade = (birthDate?: string) => {
+ if (!birthDate) return "Não informado";
+ const nascimento = new Date(birthDate);
+ const hoje = new Date();
+ let anos = hoje.getFullYear() - nascimento.getFullYear();
+ let meses = hoje.getMonth() - nascimento.getMonth();
+ if (meses < 0) {
+ anos--;
+ meses += 12;
+ }
+ return anos > 0 ? `${anos} ano${anos !== 1 ? "s" : ""}` : `${meses} mês${meses !== 1 ? "es" : ""}`;
+ };
+
+ const formatDate = (timestamp?: any) => {
+ if (!timestamp) return "-";
+ const date =
+ timestamp.toDate?.() instanceof Date
+ ? timestamp.toDate()
+ : new Date(timestamp);
+ return date.toLocaleDateString("pt-BR");
+ };
+
+ const getAnimalVaccines = (id: string) =>
+ animalHealth.vaccines.filter((v) => v.animalId === id);
+
+ const getAnimalAntiparasitics = (id: string) =>
+ animalHealth.antiparasitics.filter((a) => a.animalId === id);
+
+ const getAnimalDewormings = (id: string) =>
+ animalHealth.dewormings.filter((d) => d.animalId === id);
+
+ // Funções de remoção
+ const handleDelete = async (type: "vaccine" | "antiparasitic" | "deworming", id: string) => {
+ if (!window.confirm("Tem certeza que deseja remover este registro?")) return;
+
+ try {
+ if (type === "vaccine") {
+ await deleteVaccineRecord(id);
+ setAnimalHealth((prev) => ({
+ ...prev,
+ vaccines: prev.vaccines.filter((v) => v.id !== id),
+ }));
+ } else if (type === "antiparasitic") {
+ await deleteAntiparasiticRecord(id);
+ setAnimalHealth((prev) => ({
+ ...prev,
+ antiparasitics: prev.antiparasitics.filter((a) => a.id !== id),
+ }));
+ } else {
+ await deleteDewormingRecord(id);
+ setAnimalHealth((prev) => ({
+ ...prev,
+ dewormings: prev.dewormings.filter((d) => d.id !== id),
+ }));
+ }
+ toast.success("Registro removido com sucesso!");
+ } catch (error) {
+ console.error(error);
+ toast.error("Erro ao remover registro.");
+ }
+ };
+
+ return (
+
+
+
Carteira de Saúde
+ setSearchTerm(e.target.value)}
+ className="px-4 py-2 border border-gray-300 rounded-xl shadow-inner focus:outline-none focus:ring-2 focus:ring-blue-500 transition w-full sm:w-64"
+ />
+
+
+ {filteredAnimals.length === 0 ? (
+
+ Nenhum animal encontrado.
+
+ ) : (
+
+ {filteredAnimals.map((animal) => {
+ const vaccines = getAnimalVaccines(animal.id);
+ const antiparasitics = getAnimalAntiparasitics(animal.id);
+ const dewormings = getAnimalDewormings(animal.id);
+
+ return (
+
+
+ {animal.image ? (
+

+ ) : (
+
Sem imagem
+ )}
+
+
+
+ {animal.name}
+
+
+ {calcularIdade(animal.birthDate)} •{" "}
+ {animal.breed || "-"}
+
+
+
+
+
+ {vaccines.length} vacinas
+
+
+
+ {antiparasitics.length} antiparasitários
+
+
+
+
{dewormings.length} vermífugos
+
+
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ {selectedAnimal && (
+
+
+
+
+
+ {selectedAnimal.name} — Carteira de Saúde
+
+
+ {/* Vacinas */}
+
+
+ Vacinas
+
+
+ {getAnimalVaccines(selectedAnimal.id).length > 0 ? (
+ getAnimalVaccines(selectedAnimal.id).map((v) => (
+
+
+
ID: {v.vaccineId}
+
Aplicação: {formatDate(v.applicationDate)}
+
Próxima dose: {formatDate(v.nextDoseDate)}
+
Observações: {v.notes || "-"}
+
+
+
+ ))
+ ) : (
+
Nenhuma vacina registrada.
+ )}
+
+
+
+ {/* Antiparasitários */}
+
+
+ Antiparasitários
+
+
+ {getAnimalAntiparasitics(selectedAnimal.id).length > 0 ? (
+ getAnimalAntiparasitics(selectedAnimal.id).map((a) => (
+
+
+
ID: {a.antiparasiticId}
+
Aplicação: {formatDate(a.applicationDate)}
+
Próxima aplicação: {formatDate(a.nextApplicationDate)}
+
Observações: {a.notes || "-"}
+
+
+
+ ))
+ ) : (
+
Nenhum antiparasitário registrado.
+ )}
+
+
+
+ {/* Vermífugos */}
+
+
+ Vermífugos
+
+
+ {getAnimalDewormings(selectedAnimal.id).length > 0 ? (
+ getAnimalDewormings(selectedAnimal.id).map((d) => (
+
+
+
ID: {d.dewormingId}
+
Aplicação: {formatDate(d.applicationDate)}
+
Próxima aplicação: {formatDate(d.nextApplicationDate)}
+
Observações: {d.notes || "-"}
+
+
+
+ ))
+ ) : (
+
Nenhum vermífugo registrado.
+ )}
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/pages/Health/HealthScreen.tsx b/src/pages/Health/HealthScreen.tsx
index 990aa18..bd78cd7 100644
--- a/src/pages/Health/HealthScreen.tsx
+++ b/src/pages/Health/HealthScreen.tsx
@@ -16,9 +16,9 @@ export default function HealthRegister() {
function handleAccess() {
if (!healthType) return;
- if (healthType === "vacina") navigate("/vaccineRegister");
- if (healthType === "vermifugo") navigate("/vermifugo");
- if (healthType === "antiparasitario") navigate("/antiparasitario");
+ if (healthType === "vacina") navigate("/VaccineRegister");
+ if (healthType === "vermifugo") navigate("/DewormingRegister");
+ if (healthType === "antiparasitario") navigate("/AntiparasiticRegister");
}
return (
@@ -62,7 +62,7 @@ export default function HealthRegister() {
navigate("/health-card")}
+ onClick={() => navigate("/HealthCardList")}
className="w-full sm:w-auto flex items-center justify-center gap-2 px-1 sm:px-6 py-3 rounded-2xl bg-gray-600 text-white shadow hover:bg-gray-700 transition font-medium"
>
diff --git a/src/pages/Health/Vaccine/VaccineRegister.tsx b/src/pages/Health/Vaccine/VaccineRegister.tsx
index 5362c39..e896190 100644
--- a/src/pages/Health/Vaccine/VaccineRegister.tsx
+++ b/src/pages/Health/Vaccine/VaccineRegister.tsx
@@ -1,12 +1,14 @@
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
-import { Syringe, Save } from "lucide-react";
+import {Syringe, Save} from "lucide-react";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
import Select from "react-select";
import { useAnimals } from "../../../context/AnimalsContext.tsx";
import { getStaff } from "../../../services/staffService";
import { registerVaccine } from "../../../services/vaccineService.ts";
+import {FiArrowLeft} from "react-icons/fi";
+import {Link} from "react-router-dom";
export default function VaccineRegister() {
const [loading, setLoading] = useState(false);
@@ -183,6 +185,12 @@ export default function VaccineRegister() {
className="bg-white w-full max-w-4xl rounded-3xl shadow-xl p-6 md:p-10 space-y-8"
>
+
+ Voltar
+
Informações da Vacina
diff --git a/src/pages/Health/types/healthRecord.ts b/src/pages/Health/types/healthRecord.ts
new file mode 100644
index 0000000..52201f3
--- /dev/null
+++ b/src/pages/Health/types/healthRecord.ts
@@ -0,0 +1,15 @@
+export interface HealthRecord {
+ id: string;
+ animalId: string;
+ employeeId: string;
+ applicationDate: string;
+ nextApplicationDate?: string | null;
+ nextDoseDate?: string | null;
+ notes?: string;
+ vaccineId?: string;
+ antiparasiticId?: string;
+ dewormingId?: string;
+ booster1?: string | null;
+ booster2?: string | null;
+ booster3?: string | null;
+}
diff --git a/src/services/antiparasiticService.ts b/src/services/antiparasiticService.ts
new file mode 100644
index 0000000..9d9c1d8
--- /dev/null
+++ b/src/services/antiparasiticService.ts
@@ -0,0 +1,68 @@
+import { db } from "../lib/firebase.ts";
+import {
+ collection,
+ addDoc,
+ getDocs,
+ Timestamp,
+ deleteDoc,
+ doc,
+} from "firebase/firestore";
+import type {HealthRecord} from "../pages/Health/types/healthRecord.ts";
+
+export interface AntiparasiticData {
+ antiparasiticId: string | string[];
+ animalId: string;
+ employeeId: string;
+ applicationDate: string;
+ nextApplicationDate?: string | null;
+ notes?: string;
+}
+
+const antiparasiticCollection = collection(db, "antiparasitics");
+
+export const registerAntiparasitic = async (
+ data: AntiparasiticData & { antiparasiticId: string[] }
+) => {
+ try {
+ await Promise.all(
+ data.antiparasiticId.map(async (id) => {
+ await addDoc(antiparasiticCollection, {
+ antiparasiticId: id,
+ animalId: data.animalId,
+ employeeId: data.employeeId,
+ applicationDate: Timestamp.fromDate(new Date(data.applicationDate)),
+ nextApplicationDate: data.nextApplicationDate
+ ? Timestamp.fromDate(new Date(data.nextApplicationDate))
+ : null,
+ notes: data.notes || "",
+ createdAt: Timestamp.now(),
+ });
+ })
+ );
+ } catch (error) {
+ console.error("Erro ao registrar antiparasitário:", error);
+ throw error;
+ }
+};
+
+export const getAntiparasitics = async (): Promise => {
+ try {
+ const snapshot = await getDocs(antiparasiticCollection);
+ return snapshot.docs.map((doc) => ({
+ id: doc.id,
+ ...(doc.data() as Omit),
+ }));
+ } catch (error) {
+ console.error("Erro ao buscar antiparasitários:", error);
+ throw error;
+ }
+};
+
+export const deleteAntiparasiticRecord = async (id: string) => {
+ try {
+ await deleteDoc(doc(db, "antiparasitics", id));
+ } catch (error) {
+ console.error("Erro ao excluir antiparasitário:", error);
+ throw error;
+ }
+};
diff --git a/src/services/dewormingService.ts b/src/services/dewormingService.ts
new file mode 100644
index 0000000..4014214
--- /dev/null
+++ b/src/services/dewormingService.ts
@@ -0,0 +1,64 @@
+import { db } from "../lib/firebase.ts";
+import {
+ collection,
+ addDoc,
+ getDocs,
+ Timestamp,
+ deleteDoc,
+ doc,
+} from "firebase/firestore";
+import type {HealthRecord} from "../pages/Health/types/healthRecord.ts";
+
+interface DewormingData {
+ dewormerId: string | string[];
+ animalId: string;
+ employeeId: string;
+ applicationDate: string;
+ nextApplicationDate: string;
+ notes?: string;
+}
+
+const dewormingCollection = collection(db, "dewormings");
+
+export const registerDeworming = async (data: DewormingData & { dewormerId: string[] }) => {
+ try {
+ await Promise.all(
+ data.dewormerId.map((id) =>
+ addDoc(dewormingCollection, {
+ ...data,
+ dewormerId: id,
+ applicationDate: Timestamp.fromDate(new Date(data.applicationDate)),
+ nextApplicationDate: data.nextApplicationDate
+ ? Timestamp.fromDate(new Date(data.nextApplicationDate))
+ : null,
+ createdAt: Timestamp.now(),
+ })
+ )
+ );
+ } catch (error) {
+ console.error("Erro ao registrar vermífugo:", error);
+ throw error;
+ }
+};
+
+export const getDewormings = async (): Promise => {
+ try {
+ const snapshot = await getDocs(dewormingCollection);
+ return snapshot.docs.map((doc) => ({
+ id: doc.id,
+ ...(doc.data() as Omit),
+ }));
+ } catch (error) {
+ console.error("Erro ao buscar vermífugos:", error);
+ throw error;
+ }
+};
+
+export const deleteDewormingRecord = async (id: string) => {
+ try {
+ await deleteDoc(doc(db, "dewormings", id));
+ } catch (error) {
+ console.error("Erro ao excluir vermífugo:", error);
+ throw error;
+ }
+};
diff --git a/src/services/vaccineService.ts b/src/services/vaccineService.ts
index 93b1684..1bef136 100644
--- a/src/services/vaccineService.ts
+++ b/src/services/vaccineService.ts
@@ -4,7 +4,10 @@ import {
addDoc,
getDocs,
Timestamp,
+ deleteDoc,
+ doc,
} from "firebase/firestore";
+import type {HealthRecord} from "../pages/Health/types/healthRecord.ts";
interface VaccineData {
vaccineId: string | string[];
@@ -23,7 +26,7 @@ const vaccineCollection = collection(db, "vaccines");
export const registerVaccine = async (data: VaccineData & { vaccineId: string[] }) => {
try {
await Promise.all(
- data.vaccineId.map(id =>
+ data.vaccineId.map((id) =>
addDoc(vaccineCollection, {
...data,
vaccineId: id,
@@ -41,15 +44,24 @@ export const registerVaccine = async (data: VaccineData & { vaccineId: string[]
}
};
-export const getVaccines = async () => {
+export const getVaccines = async (): Promise => {
try {
const snapshot = await getDocs(vaccineCollection);
return snapshot.docs.map((doc) => ({
id: doc.id,
- ...doc.data(),
+ ...(doc.data() as Omit),
}));
} catch (error) {
console.error("Erro ao buscar vacinas:", error);
throw error;
}
};
+
+export const deleteVaccineRecord = async (id: string) => {
+ try {
+ await deleteDoc(doc(db, "vaccines", id));
+ } catch (error) {
+ console.error("Erro ao excluir vacina:", error);
+ throw error;
+ }
+};