From 3636f51a32a457d59ebe33d0cb11751828d5c555 Mon Sep 17 00:00:00 2001 From: Rafael Date: Sun, 9 Nov 2025 16:05:43 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adiciona=20sistema=20de=20vacina=C3=A7?= =?UTF-8?q?=C3=A3o,=20vermifugos=20e=20antiparasitarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 37 +- src/App.tsx | 6 + .../AntiparasiticRegister.tsx | 369 ++++++++++++++++++ .../DewormingRegister/DewormingRegister.tsx | 352 +++++++++++++++++ .../Health/HealthCardList/HealthCardList.tsx | 325 +++++++++++++++ src/pages/Health/HealthScreen.tsx | 8 +- src/pages/Health/Vaccine/VaccineRegister.tsx | 10 +- src/pages/Health/types/healthRecord.ts | 15 + src/services/antiparasiticService.ts | 68 ++++ src/services/dewormingService.ts | 64 +++ src/services/vaccineService.ts | 18 +- 11 files changed, 1247 insertions(+), 25 deletions(-) create mode 100644 src/pages/Health/AntiparasiticRegister/AntiparasiticRegister.tsx create mode 100644 src/pages/Health/DewormingRegister/DewormingRegister.tsx create mode 100644 src/pages/Health/HealthCardList/HealthCardList.tsx create mode 100644 src/pages/Health/types/healthRecord.ts create mode 100644 src/services/antiparasiticService.ts create mode 100644 src/services/dewormingService.ts 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 +

+ +
+
+ + { + setSelectedAnimal(option); + setFormData({ + ...formData, + animalId: option?.value || "", + }); + setErrors({ ...errors, animalId: "" }); + }} + placeholder="Selecione" + styles={selectStyles(!!errors.animalId)} + /> + {errors.animalId && ( + {errors.animalId} + )} +
+ +
+ + + {errors.applicationDate && ( + + {errors.applicationDate} + + )} +
+ +
+ + + {errors.nextApplicationDate && ( + + {errors.nextApplicationDate} + + )} +
+
+
+ +
+ +