From dddb818b0f547b762f9265849cfec78dc64f72f4 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Sun, 2 Nov 2025 17:01:05 -0300 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20adiciona=20tela=20de=20sa=C3=BAde?= =?UTF-8?q?=20ao=20sistema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 7 ++- src/Components/Sidebar/Sidebar.tsx | 2 +- src/pages/Health/HealthScreen.tsx | 90 ++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/pages/Health/HealthScreen.tsx diff --git a/src/App.tsx b/src/App.tsx index f3a68bd..68ef389 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import AnimalList from "./pages/Animal/AnimalList.tsx"; import StaffRegister from "./pages/Staff/StaffRegister.tsx"; import StaffList from "./pages/Staff/StaffList.tsx"; import Dashboard from "./pages/Dashboard.tsx"; -import VaccineRegister from "./pages/VaccineRegister.tsx"; +import HealthScreen from "./pages/Health/HealthScreen.tsx"; import AdoptionRegister from "./pages/Adoption/AdoptionRegister.tsx"; import AdoptionList from "./pages/Adoption/AdoptionList.tsx"; import AdopterRegister from "./pages/Adopter/AdopterRegister.tsx"; @@ -17,6 +17,7 @@ import {Toaster} from "react-hot-toast"; import type {JSX} from "react"; import {AnimalsProvider} from "./context/AnimalsContext.tsx"; import { AdoptionsProvider } from "./context/AdoptionsContext"; +import VaccineRegister from "./pages/Health/Vaccine/VaccineRegister.tsx"; const ProtectedLayout = ({children}: { children: JSX.Element }) => (
@@ -54,7 +55,9 @@ export default function App() { }/>}/> }/>}/> - }/>}/> + }/>}/> + }/>}/> + }/>}/> }/>}/> diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx index 65091ca..f820165 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Sidebar/Sidebar.tsx @@ -23,7 +23,7 @@ function SideBar() { { id: "animals", label: "Animais", icon: PawPrint, href: "/animals" }, { id: "staff", label: "Funcionários", icon: Briefcase, href: "/staff" }, { id: "adopter", label: "Adotantes", icon: HeartHandshake, href: "/adopter" }, - { id: "vaccines", label: "Vacinas", icon: Archive, href: "/vaccines" }, + { id: "health", label: "Saúde", icon: Archive, href: "/health" }, { id: "adoptions", label: "Adoções", icon: FileText, href: "/adoptions" }, ]; diff --git a/src/pages/Health/HealthScreen.tsx b/src/pages/Health/HealthScreen.tsx new file mode 100644 index 0000000..990aa18 --- /dev/null +++ b/src/pages/Health/HealthScreen.tsx @@ -0,0 +1,90 @@ +import { useState } from "react"; +import { motion } from "framer-motion"; +import { Syringe, ClipboardEdit } from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +export default function HealthRegister() { + const [healthType, setHealthType] = useState(""); + const navigate = useNavigate(); + + 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"); + + function handleAccess() { + if (!healthType) return; + + if (healthType === "vacina") navigate("/vaccineRegister"); + if (healthType === "vermifugo") navigate("/vermifugo"); + if (healthType === "antiparasitario") navigate("/antiparasitario"); + } + + return ( +
+ + + Registro de Saúde + + + +
+

Informações de Saúde

+
+
+ + +
+
+
+ +
+ navigate("/health-card")} + 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" + > + + Carteira de Saúde + + + + Acessar + +
+
+
+ ); +} From 69c4d4a9b792c1f664640d25f6348266fb6cc64c Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Sun, 2 Nov 2025 17:01:32 -0300 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20adiciona=20tela=20e=20servi=C3=A7o?= =?UTF-8?q?=20de=20registro=20de=20vacinas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Health/Vaccine/VaccineRegister.tsx | 325 +++++++++++++++++++ src/pages/VaccineRegister.tsx | 69 ---- src/services/vaccineService.ts | 55 ++++ 3 files changed, 380 insertions(+), 69 deletions(-) create mode 100644 src/pages/Health/Vaccine/VaccineRegister.tsx delete mode 100644 src/pages/VaccineRegister.tsx create mode 100644 src/services/vaccineService.ts diff --git a/src/pages/Health/Vaccine/VaccineRegister.tsx b/src/pages/Health/Vaccine/VaccineRegister.tsx new file mode 100644 index 0000000..5362c39 --- /dev/null +++ b/src/pages/Health/Vaccine/VaccineRegister.tsx @@ -0,0 +1,325 @@ +import { useEffect, useState } from "react"; +import { motion } from "framer-motion"; +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"; + +export default function VaccineRegister() { + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + const { animals } = useAnimals(); + + const [formData, setFormData] = useState({ + vaccineId: [] as string[], + animalId: "", + employeeId: "", + applicationDate: "", + nextDoseDate: "", + notes: "", + booster1: "", + booster2: "", + booster3:"", + }); + + const [errors, setErrors] = useState<{ [key: string]: string }>({}); + const [employees, setEmployees] = useState([]); + const [selectedVaccine, setSelectedVaccine] = useState([]); + const [selectedAnimal, setSelectedAnimal] = useState(null); + const [selectedEmployee, setSelectedEmployee] = useState(null); + + const vaccineOptions = [ + { + label: "Cães", + options: [ + { value: "vacina_antirrabica", label: "Antirrábica" }, + { value: "vacina_v8", label: "V8" }, + { value: "vacina_v10", label: "V10" }, + { value: "vacina_gripe_canina", label: "Gripe Canina" }, + { value: "vacina_giardia", label: "Giárdia" }, + ], + }, + { + label: "Gatos", + options: [ + { value: "vacina_antirabica_gato", label: "Antirrábica" }, + { value: "vacina_v3", label: "V3" }, + { value: "vacina_v4", label: "V4" }, + { value: "vacina_v5", label: "V5" }, + ], + }, + ]; + + 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 = ["vaccineId", "animalId", "employeeId", "applicationDate"]; + 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 registerVaccine(formData); + toast.success("Vacinação registrada com sucesso!"); + + setFormData({ + vaccineId: [], + animalId: "", + employeeId: "", + applicationDate: "", + nextDoseDate: "", + notes: "", + booster1: "", + booster2: "", + booster3: "", + }); + setSelectedVaccine([]); + setSelectedAnimal(null); + setSelectedEmployee(null); + setErrors({}); + } catch (error) { + console.error(error); + toast.error("Erro ao registrar vacinaçã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", + }), + multiValueLabel: (base: any) => ({ + ...base, + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + }), + menu: (base: any) => ({ + ...base, + borderRadius: "0.75rem", + zIndex: 20, + }), + }); + + + + return ( +
+ + Registro de Vacinação + + + +
+

Informações da Vacina

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