forked from RiseUP/riseup-squad21
Merge pull request 'Doctor-e-Paciente' (#11) from StsDanilo/riseup-squad21:Doctor-e-Paciente into main
Reviewed-on: RiseUP/riseup-squad21#11
This commit is contained in:
commit
a650e9777a
156
app/cadastro/page.tsx
Normal file
156
app/cadastro/page.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Calendar, Clock, User, Shield, Stethoscope, Receipt, IdCard } from "lucide-react"
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">Sistema de Consultas Médicas</h1>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||
Gerencie suas consultas médicas de forma simples e eficiente
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<User className="w-12 h-12 text-black-600 mx-auto mb-4" />
|
||||
<CardTitle>Área do Paciente</CardTitle>
|
||||
<CardDescription>Acesse sua área pessoal para agendar consultas e gerenciar seus dados</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Agendar consultas</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Ver histórico de consultas</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Gerenciar dados pessoais</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/patient/login" className="block mt-auto">
|
||||
<Button className="w-full">Entrar como Paciente</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<Shield className="w-12 h-12 text-purple-600 mx-auto mb-4" />
|
||||
<CardTitle>Área da Secretária</CardTitle>
|
||||
<CardDescription>Gerencie consultas, pacientes e agenda médica</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Gerenciar consultas</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Cadastrar pacientes</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Controlar agenda</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/secretary/login" className="block mt-auto">
|
||||
<Button className="w-full bg-purple-600 hover:bg-purple-700">Entrar como Secretária</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<Stethoscope className="w-12 h-12 text-green-600 mx-auto mb-4" />
|
||||
<CardTitle>Área Médica</CardTitle>
|
||||
<CardDescription>Acesso restrito para profissionais de saúde</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Gerenciar agenda</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Ver pacientes</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Histórico de atendimentos</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/doctor/login" className="block mt-auto">
|
||||
<Button className="w-full bg-green-600 hover:bg-green-700">Entrar como Médico</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<IdCard className="w-12 h-12 text-blue-600 mx-auto mb-4" />
|
||||
<CardTitle>Área do Gestor</CardTitle>
|
||||
<CardDescription>Acesso restrito para gestores e coordenadores</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Relatórios gerenciais</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Configurações do sistema</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Gestão de usuários</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="#" className="block mt-auto">
|
||||
<Button className="w-full bg-blue-600 hover:bg-blue-700">Entrar como Gestor</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<Receipt className="w-12 h-12 text-orange-600 mx-auto mb-4" />
|
||||
<CardTitle>Área de Finanças</CardTitle>
|
||||
<CardDescription>Acesso restrito para profissionais do setor financeiro</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Relatórios financeiros</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Faturamento</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Controle de pagamentos</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="#" className="block mt-auto">
|
||||
<Button className="w-full bg-orange-600 hover:bg-orange-700">Entrar como Financeiro</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
257
app/page.tsx
257
app/page.tsx
@ -1,156 +1,115 @@
|
||||
|
||||
"use client";
|
||||
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Calendar, Clock, User, Shield, Stethoscope, Receipt, IdCard } from "lucide-react"
|
||||
|
||||
export default function HomePage() {
|
||||
|
||||
export default function InicialPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">Sistema de Consultas Médicas</h1>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||
Gerencie suas consultas médicas de forma simples e eficiente
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<User className="w-12 h-12 text-black-600 mx-auto mb-4" />
|
||||
<CardTitle>Área do Paciente</CardTitle>
|
||||
<CardDescription>Acesse sua área pessoal para agendar consultas e gerenciar seus dados</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Agendar consultas</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Ver histórico de consultas</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Gerenciar dados pessoais</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/patient/login" className="block mt-auto">
|
||||
<Button className="w-full">Entrar como Paciente</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<Shield className="w-12 h-12 text-purple-600 mx-auto mb-4" />
|
||||
<CardTitle>Área da Secretária</CardTitle>
|
||||
<CardDescription>Gerencie consultas, pacientes e agenda médica</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Gerenciar consultas</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Cadastrar pacientes</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Controlar agenda</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/secretary/login" className="block mt-auto">
|
||||
<Button className="w-full bg-purple-600 hover:bg-purple-700">Entrar como Secretária</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<Stethoscope className="w-12 h-12 text-green-600 mx-auto mb-4" />
|
||||
<CardTitle>Área Médica</CardTitle>
|
||||
<CardDescription>Acesso restrito para profissionais de saúde</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Gerenciar agenda</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Ver pacientes</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Histórico de atendimentos</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/doctor/login" className="block mt-auto">
|
||||
<Button className="w-full bg-green-600 hover:bg-green-700">Entrar como Médico</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<IdCard className="w-12 h-12 text-blue-600 mx-auto mb-4" />
|
||||
<CardTitle>Área do Gestor</CardTitle>
|
||||
<CardDescription>Acesso restrito para gestores e coordenadores</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Relatórios gerenciais</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Configurações do sistema</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Gestão de usuários</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/manager/login" className="block mt-auto">
|
||||
<Button className="w-full bg-blue-600 hover:bg-blue-700">Entrar como Gestor</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow flex flex-col h-full">
|
||||
<CardHeader className="text-center flex-shrink-0">
|
||||
<Receipt className="w-12 h-12 text-orange-600 mx-auto mb-4" />
|
||||
<CardTitle>Área de Finanças</CardTitle>
|
||||
<CardDescription>Acesso restrito para profissionais do setor financeiro</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-grow flex flex-col">
|
||||
<div className="space-y-3 flex-grow">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Relatórios financeiros</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Faturamento</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Controle de pagamentos</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/finance/login" className="block mt-auto">
|
||||
<Button className="w-full bg-orange-600 hover:bg-orange-700">Entrar como Financeiro</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="min-h-screen flex flex-col bg-gray-50">
|
||||
{}
|
||||
<div className="bg-black text-white text-sm py-2 px-6 flex justify-between">
|
||||
<span> Horário: 08h00 - 21h00</span>
|
||||
<span> Email: contato@midconnecta.com</span>
|
||||
</div>
|
||||
|
||||
{}
|
||||
<header className="bg-white shadow-md py-4 px-6 flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-blue-700">MidConnecta</h1>
|
||||
<nav className="flex space-x-6 text-gray-700 font-medium">
|
||||
<a href="#home" className="hover:text-blue-600">Home</a>
|
||||
<a href="#about" className="hover:text-blue-600">Sobre</a>
|
||||
<a href="#departments" className="hover:text-blue-600">Departamentos</a>
|
||||
<a href="#doctors" className="hover:text-blue-600">Médicos</a>
|
||||
<a href="#contact" className="hover:text-blue-600">Contato</a>
|
||||
</nav>
|
||||
<div className="flex space-x-4">
|
||||
{}
|
||||
<Link href="/cadastro">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="rounded-full px-6 py-2 border-2 border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white transition cursor-pointer"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{}
|
||||
<section className="flex flex-col md:flex-row items-center justify-between px-10 md:px-20 py-16 bg-gray-100">
|
||||
<div className="max-w-lg">
|
||||
<h2 className="text-gray-600 uppercase text-sm">Bem-vindo à Saúde Digital</h2>
|
||||
<h1 className="text-4xl font-extrabold text-black leading-tight mt-2">
|
||||
Soluções Médicas <br /> & Cuidados com a Saúde
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-4">
|
||||
São mais de 25 anos de experiência em serviços médicos com qualidade e confiança.
|
||||
</p>
|
||||
<div className="mt-6 flex space-x-4">
|
||||
<Button className="rounded-full px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white shadow-lg transition">
|
||||
Nossos Serviços
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="rounded-full px-6 py-2 border-2 border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white transition"
|
||||
>
|
||||
Saiba Mais
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-10 md:mt-0">
|
||||
<img
|
||||
src="https://cdn-icons-png.flaticon.com/512/387/387561.png"
|
||||
alt="Médico"
|
||||
className="w-80"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{}
|
||||
<section className="py-16 px-10 md:px-20 bg-white">
|
||||
<h2 className="text-center text-3xl font-bold text-black">Nossos Serviços</h2>
|
||||
<p className="text-center text-gray-600 mt-2">Serviços médicos que oferecemos</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-10">
|
||||
<div className="p-6 bg-gray-100 rounded-xl shadow hover:shadow-lg transition">
|
||||
<h3 className="text-xl font-semibold text-blue-600">Clínica Geral</h3>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Atendimento médico geral com foco na prevenção e diagnóstico.
|
||||
</p>
|
||||
<Button className="mt-4 rounded-full bg-blue-600 hover:bg-blue-700 text-white px-5 py-2">
|
||||
Agendar
|
||||
</Button>
|
||||
</div>
|
||||
<div className="p-6 bg-gray-100 rounded-xl shadow hover:shadow-lg transition">
|
||||
<h3 className="text-xl font-semibold text-blue-600">Pediatria</h3>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Cuidado especializado para crianças e adolescentes.
|
||||
</p>
|
||||
<Button className="mt-4 rounded-full bg-blue-600 hover:bg-blue-700 text-white px-5 py-2">
|
||||
Agendar
|
||||
</Button>
|
||||
</div>
|
||||
<div className="p-6 bg-gray-100 rounded-xl shadow hover:shadow-lg transition">
|
||||
<h3 className="text-xl font-semibold text-blue-600">Exames</h3>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Exames laboratoriais e de imagem com precisão e agilidade.
|
||||
</p>
|
||||
<Button className="mt-4 rounded-full bg-blue-600 hover:bg-blue-700 text-white px-5 py-2">
|
||||
Agendar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{}
|
||||
<footer className="bg-black text-white py-6 text-center">
|
||||
<p>© 2025 MidConnecta</p>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -14,56 +14,13 @@ import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
|
||||
// Mock data - in a real app, this would come from an API
|
||||
const mockPatients = [
|
||||
{
|
||||
id: 1,
|
||||
nome: "Aaron Avalos Perez",
|
||||
cpf: "123.456.789-00",
|
||||
rg: "12.345.678-9",
|
||||
sexo: "masculino",
|
||||
dataNascimento: "1990-01-15",
|
||||
etnia: "branca",
|
||||
raca: "caucasiana",
|
||||
naturalidade: "Aracaju",
|
||||
nacionalidade: "brasileira",
|
||||
profissao: "Engenheiro",
|
||||
estadoCivil: "solteiro",
|
||||
nomeMae: "Maria Perez",
|
||||
profissaoMae: "Professora",
|
||||
nomePai: "João Perez",
|
||||
profissaoPai: "Médico",
|
||||
nomeResponsavel: "",
|
||||
cpfResponsavel: "",
|
||||
nomeEsposo: "",
|
||||
email: "aaron@email.com",
|
||||
celular: "(79) 99943-2499",
|
||||
telefone1: "(79) 3214-5678",
|
||||
telefone2: "",
|
||||
cep: "49000-000",
|
||||
endereco: "Rua das Flores, 123",
|
||||
numero: "123",
|
||||
complemento: "Apt 101",
|
||||
bairro: "Centro",
|
||||
cidade: "Aracaju",
|
||||
estado: "SE",
|
||||
tipoSanguineo: "O+",
|
||||
peso: "75",
|
||||
altura: "1.75",
|
||||
alergias: "Nenhuma alergia conhecida",
|
||||
convenio: "Particular",
|
||||
plano: "Premium",
|
||||
numeroMatricula: "123456789",
|
||||
validadeCarteira: "2025-12-31",
|
||||
observacoes: "Paciente colaborativo",
|
||||
},
|
||||
];
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import { json } from "stream/consumers";
|
||||
|
||||
export default function EditarPacientePage() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const patientId = Number.parseInt(params.id as string);
|
||||
const patientId = params.id;
|
||||
const { toast } = useToast();
|
||||
|
||||
// Photo upload state
|
||||
@ -75,45 +32,107 @@ export default function EditarPacientePage() {
|
||||
const [isUploadingAnexo, setIsUploadingAnexo] = useState(false);
|
||||
const anexoInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
type FormData = {
|
||||
nome: string; // full_name
|
||||
cpf: string;
|
||||
dataNascimento: string; // birth_date
|
||||
sexo: string; // sex
|
||||
id?: string;
|
||||
nomeSocial?: string; // social_name
|
||||
rg?: string;
|
||||
documentType?: string; // document_type
|
||||
documentNumber?: string; // document_number
|
||||
ethnicity?: string;
|
||||
race?: string;
|
||||
naturality?: string;
|
||||
nationality?: string;
|
||||
profession?: string;
|
||||
maritalStatus?: string; // marital_status
|
||||
motherName?: string; // mother_name
|
||||
motherProfession?: string; // mother_profession
|
||||
fatherName?: string; // father_name
|
||||
fatherProfession?: string; // father_profession
|
||||
guardianName?: string; // guardian_name
|
||||
guardianCpf?: string; // guardian_cpf
|
||||
spouseName?: string; // spouse_name
|
||||
rnInInsurance?: boolean; // rn_in_insurance
|
||||
legacyCode?: string; // legacy_code
|
||||
notes?: string;
|
||||
email?: string;
|
||||
phoneMobile?: string; // phone_mobile
|
||||
phone1?: string;
|
||||
phone2?: string;
|
||||
cep?: string;
|
||||
street?: string;
|
||||
number?: string;
|
||||
complement?: string;
|
||||
neighborhood?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
reference?: string;
|
||||
vip?: boolean;
|
||||
lastVisitAt?: string;
|
||||
nextAppointmentAt?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
createdBy?: string;
|
||||
updatedBy?: string;
|
||||
weightKg?: string;
|
||||
heightM?: string;
|
||||
bmi?: string;
|
||||
bloodType?: string;
|
||||
};
|
||||
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
nome: "",
|
||||
cpf: "",
|
||||
rg: "",
|
||||
sexo: "",
|
||||
dataNascimento: "",
|
||||
etnia: "",
|
||||
raca: "",
|
||||
naturalidade: "",
|
||||
nacionalidade: "",
|
||||
profissao: "",
|
||||
estadoCivil: "",
|
||||
nomeMae: "",
|
||||
profissaoMae: "",
|
||||
nomePai: "",
|
||||
profissaoPai: "",
|
||||
nomeResponsavel: "",
|
||||
cpfResponsavel: "",
|
||||
nomeEsposo: "",
|
||||
sexo: "",
|
||||
id: "",
|
||||
nomeSocial: "",
|
||||
rg: "",
|
||||
documentType: "",
|
||||
documentNumber: "",
|
||||
ethnicity: "",
|
||||
race: "",
|
||||
naturality: "",
|
||||
nationality: "",
|
||||
profession: "",
|
||||
maritalStatus: "",
|
||||
motherName: "",
|
||||
motherProfession: "",
|
||||
fatherName: "",
|
||||
fatherProfession: "",
|
||||
guardianName: "",
|
||||
guardianCpf: "",
|
||||
spouseName: "",
|
||||
rnInInsurance: false,
|
||||
legacyCode: "",
|
||||
notes: "",
|
||||
email: "",
|
||||
celular: "",
|
||||
telefone1: "",
|
||||
telefone2: "",
|
||||
phoneMobile: "",
|
||||
phone1: "",
|
||||
phone2: "",
|
||||
cep: "",
|
||||
endereco: "",
|
||||
numero: "",
|
||||
complemento: "",
|
||||
bairro: "",
|
||||
cidade: "",
|
||||
estado: "",
|
||||
tipoSanguineo: "",
|
||||
peso: "",
|
||||
altura: "",
|
||||
alergias: "",
|
||||
convenio: "",
|
||||
plano: "",
|
||||
numeroMatricula: "",
|
||||
validadeCarteira: "",
|
||||
observacoes: "",
|
||||
street: "",
|
||||
number: "",
|
||||
complement: "",
|
||||
neighborhood: "",
|
||||
city: "",
|
||||
state: "",
|
||||
reference: "",
|
||||
vip: false,
|
||||
lastVisitAt: "",
|
||||
nextAppointmentAt: "",
|
||||
createdAt: "",
|
||||
updatedAt: "",
|
||||
createdBy: "",
|
||||
updatedBy: "",
|
||||
weightKg: "",
|
||||
heightM: "",
|
||||
bmi: "",
|
||||
bloodType: "",
|
||||
});
|
||||
|
||||
const [isGuiaConvenio, setIsGuiaConvenio] = useState(false);
|
||||
@ -122,169 +141,66 @@ export default function EditarPacientePage() {
|
||||
useEffect(() => {
|
||||
async function fetchPatient() {
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json = await res.json();
|
||||
const p = json?.data || json;
|
||||
const res = await patientsService.getById(patientId);
|
||||
// Map API snake_case/nested to local camelCase form
|
||||
setFormData({
|
||||
nome: p?.nome ?? "",
|
||||
cpf: p?.cpf ?? "",
|
||||
rg: p?.rg ?? "",
|
||||
sexo: p?.sexo ?? "",
|
||||
dataNascimento: p?.data_nascimento ?? p?.dataNascimento ?? "",
|
||||
etnia: p?.etnia ?? "",
|
||||
raca: p?.raca ?? "",
|
||||
naturalidade: p?.naturalidade ?? "",
|
||||
nacionalidade: p?.nacionalidade ?? "",
|
||||
profissao: p?.profissao ?? "",
|
||||
estadoCivil: p?.estado_civil ?? p?.estadoCivil ?? "",
|
||||
nomeMae: p?.nome_mae ?? p?.nomeMae ?? "",
|
||||
profissaoMae: p?.profissao_mae ?? p?.profissaoMae ?? "",
|
||||
nomePai: p?.nome_pai ?? p?.nomePai ?? "",
|
||||
profissaoPai: p?.profissao_pai ?? p?.profissaoPai ?? "",
|
||||
nomeResponsavel: p?.nome_responsavel ?? p?.nomeResponsavel ?? "",
|
||||
cpfResponsavel: p?.cpf_responsavel ?? p?.cpfResponsavel ?? "",
|
||||
nomeEsposo: p?.nome_esposo ?? p?.nomeEsposo ?? "",
|
||||
email: p?.contato?.email ?? p?.email ?? "",
|
||||
celular: p?.contato?.celular ?? p?.celular ?? "",
|
||||
telefone1: p?.contato?.telefone1 ?? p?.telefone1 ?? "",
|
||||
telefone2: p?.contato?.telefone2 ?? p?.telefone2 ?? "",
|
||||
cep: p?.endereco?.cep ?? p?.cep ?? "",
|
||||
endereco: p?.endereco?.logradouro ?? p?.endereco ?? "",
|
||||
numero: p?.endereco?.numero ?? p?.numero ?? "",
|
||||
complemento: p?.endereco?.complemento ?? p?.complemento ?? "",
|
||||
bairro: p?.endereco?.bairro ?? p?.bairro ?? "",
|
||||
cidade: p?.endereco?.cidade ?? p?.cidade ?? "",
|
||||
estado: p?.endereco?.estado ?? p?.estado ?? "",
|
||||
tipoSanguineo: p?.tipo_sanguineo ?? p?.tipoSanguineo ?? "",
|
||||
peso: p?.peso ? String(p.peso) : "",
|
||||
altura: p?.altura ? String(p.altura) : "",
|
||||
alergias: p?.alergias ?? "",
|
||||
convenio: p?.convenio ?? "",
|
||||
plano: p?.plano ?? "",
|
||||
numeroMatricula: p?.numero_matricula ?? p?.numeroMatricula ?? "",
|
||||
validadeCarteira: p?.validade_carteira ?? p?.validadeCarteira ?? "",
|
||||
observacoes: p?.observacoes ?? "",
|
||||
id: res[0]?.id ?? "",
|
||||
nome: res[0]?.full_name ?? "",
|
||||
nomeSocial: res[0]?.social_name ?? "",
|
||||
cpf: res[0]?.cpf ?? "",
|
||||
rg: res[0]?.rg ?? "",
|
||||
documentType: res[0]?.document_type ?? "",
|
||||
documentNumber: res[0]?.document_number ?? "",
|
||||
sexo: res[0]?.sex ?? "",
|
||||
dataNascimento: res[0]?.birth_date ?? "",
|
||||
ethnicity: res[0]?.ethnicity ?? "",
|
||||
race: res[0]?.race ?? "",
|
||||
naturality: res[0]?.naturality ?? "",
|
||||
nationality: res[0]?.nationality ?? "",
|
||||
profession: res[0]?.profession ?? "",
|
||||
maritalStatus: res[0]?.marital_status ?? "",
|
||||
motherName: res[0]?.mother_name ?? "",
|
||||
motherProfession: res[0]?.mother_profession ?? "",
|
||||
fatherName: res[0]?.father_name ?? "",
|
||||
fatherProfession: res[0]?.father_profession ?? "",
|
||||
guardianName: res[0]?.guardian_name ?? "",
|
||||
guardianCpf: res[0]?.guardian_cpf ?? "",
|
||||
spouseName: res[0]?.spouse_name ?? "",
|
||||
rnInInsurance: res[0]?.rn_in_insurance ?? false,
|
||||
legacyCode: res[0]?.legacy_code ?? "",
|
||||
notes: res[0]?.notes ?? "",
|
||||
email: res[0]?.email ?? "",
|
||||
phoneMobile: res[0]?.phone_mobile ?? "",
|
||||
phone1: res[0]?.phone1 ?? "",
|
||||
phone2: res[0]?.phone2 ?? "",
|
||||
cep: res[0]?.cep ?? "",
|
||||
street: res[0]?.street ?? "",
|
||||
number: res[0]?.number ?? "",
|
||||
complement: res[0]?.complement ?? "",
|
||||
neighborhood: res[0]?.neighborhood ?? "",
|
||||
city: res[0]?.city ?? "",
|
||||
state: res[0]?.state ?? "",
|
||||
reference: res[0]?.reference ?? "",
|
||||
vip: res[0]?.vip ?? false,
|
||||
lastVisitAt: res[0]?.last_visit_at ?? "",
|
||||
nextAppointmentAt: res[0]?.next_appointment_at ?? "",
|
||||
createdAt: res[0]?.created_at ?? "",
|
||||
updatedAt: res[0]?.updated_at ?? "",
|
||||
createdBy: res[0]?.created_by ?? "",
|
||||
updatedBy: res[0]?.updated_by ?? "",
|
||||
weightKg: res[0]?.weight_kg ? String(res[0].weight_kg) : "",
|
||||
heightM: res[0]?.height_m ? String(res[0].height_m) : "",
|
||||
bmi: res[0]?.bmi ? String(res[0].bmi) : "",
|
||||
bloodType: res[0]?.blood_type ?? "",
|
||||
});
|
||||
const foto = p?.foto_url || p?.fotoUrl;
|
||||
if (foto) setPhotoUrl(foto);
|
||||
|
||||
} catch (e: any) {
|
||||
toast({ title: "Erro", description: e?.message || "Falha ao carregar paciente" });
|
||||
}
|
||||
}
|
||||
async function fetchAnexos() {
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}/anexos`);
|
||||
if (!res.ok) return;
|
||||
const json = await res.json();
|
||||
const items = Array.isArray(json?.data) ? json.data : json;
|
||||
setAnexos(Array.isArray(items) ? items : []);
|
||||
} catch {}
|
||||
}
|
||||
fetchPatient();
|
||||
fetchAnexos();
|
||||
}, [patientId, toast]);
|
||||
|
||||
const onPickPhoto = () => fileInputRef.current?.click();
|
||||
|
||||
const onPhotoSelected = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
setIsUploadingPhoto(true);
|
||||
const form = new FormData();
|
||||
// Common field name: 'foto'; also append 'file' for compatibility with some mocks
|
||||
form.append("foto", file);
|
||||
form.append("file", file);
|
||||
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}/foto`, {
|
||||
method: "POST",
|
||||
body: form,
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Falha no upload (HTTP ${res.status})`);
|
||||
}
|
||||
let msg = "Foto enviada com sucesso";
|
||||
try {
|
||||
const payload = await res.json();
|
||||
if (payload?.success === false) {
|
||||
throw new Error(payload?.message || "A API retornou erro");
|
||||
}
|
||||
if (payload?.message) msg = String(payload.message);
|
||||
if (payload?.data?.foto_url || payload?.foto_url || payload?.url) {
|
||||
setPhotoUrl(payload.data?.foto_url ?? payload.foto_url ?? payload.url);
|
||||
}
|
||||
} catch {
|
||||
// Ignore JSON parse errors
|
||||
}
|
||||
toast({ title: "Sucesso", description: msg });
|
||||
} catch (err: any) {
|
||||
toast({ title: "Erro", description: err?.message || "Não foi possível enviar a foto" });
|
||||
} finally {
|
||||
setIsUploadingPhoto(false);
|
||||
// clear the input to allow re-selecting the same file
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
// Remove patient photo via API
|
||||
const onRemovePhoto = async () => {
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}/foto`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!res.ok) throw new Error(`Falha ao remover foto (HTTP ${res.status})`);
|
||||
setPhotoUrl(null);
|
||||
toast({ title: "Sucesso", description: "Foto removida" });
|
||||
} catch (err: any) {
|
||||
toast({ title: "Erro", description: err?.message || "Não foi possível remover a foto" });
|
||||
}
|
||||
};
|
||||
|
||||
// Anexos helpers
|
||||
const onPickAnexo = () => anexoInputRef.current?.click();
|
||||
|
||||
const onAnexoSelected = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
setIsUploadingAnexo(true);
|
||||
const form = new FormData();
|
||||
form.append("anexo", file);
|
||||
form.append("file", file);
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}/anexos`, {
|
||||
method: "POST",
|
||||
body: form,
|
||||
});
|
||||
if (!res.ok) throw new Error(`Falha ao enviar anexo (HTTP ${res.status})`);
|
||||
// Refresh anexos list
|
||||
try {
|
||||
const refreshed = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}/anexos`);
|
||||
const json = await refreshed.json();
|
||||
const items = Array.isArray(json?.data) ? json.data : json;
|
||||
setAnexos(Array.isArray(items) ? items : []);
|
||||
} catch {}
|
||||
toast({ title: "Sucesso", description: "Anexo adicionado" });
|
||||
} catch (err: any) {
|
||||
toast({ title: "Erro", description: err?.message || "Não foi possível enviar o anexo" });
|
||||
} finally {
|
||||
setIsUploadingAnexo(false);
|
||||
if (anexoInputRef.current) anexoInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteAnexo = async (anexoId: string | number) => {
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}/anexos/${anexoId}`, { method: "DELETE" });
|
||||
if (!res.ok) throw new Error(`Falha ao remover anexo (HTTP ${res.status})`);
|
||||
setAnexos((prev) => prev.filter((a) => String(a.id) !== String(anexoId)));
|
||||
toast({ title: "Sucesso", description: "Anexo removido" });
|
||||
} catch (err: any) {
|
||||
toast({ title: "Erro", description: err?.message || "Não foi possível remover o anexo" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
@ -293,97 +209,43 @@ export default function EditarPacientePage() {
|
||||
e.preventDefault();
|
||||
// Build API payload (snake_case)
|
||||
const payload = {
|
||||
nome: formData.nome,
|
||||
cpf: formData.cpf,
|
||||
rg: formData.rg || null,
|
||||
sexo: formData.sexo || null,
|
||||
data_nascimento: formData.dataNascimento || null,
|
||||
etnia: formData.etnia || null,
|
||||
raca: formData.raca || null,
|
||||
naturalidade: formData.naturalidade || null,
|
||||
nacionalidade: formData.nacionalidade || null,
|
||||
profissao: formData.profissao || null,
|
||||
estado_civil: formData.estadoCivil || null,
|
||||
nome_mae: formData.nomeMae || null,
|
||||
profissao_mae: formData.profissaoMae || null,
|
||||
nome_pai: formData.nomePai || null,
|
||||
profissao_pai: formData.profissaoPai || null,
|
||||
nome_responsavel: formData.nomeResponsavel || null,
|
||||
cpf_responsavel: formData.cpfResponsavel || null,
|
||||
contato: {
|
||||
email: formData.email || null,
|
||||
celular: formData.celular || null,
|
||||
telefone1: formData.telefone1 || null,
|
||||
telefone2: formData.telefone2 || null,
|
||||
},
|
||||
endereco: {
|
||||
cep: formData.cep || null,
|
||||
logradouro: formData.endereco || null,
|
||||
numero: formData.numero || null,
|
||||
complemento: formData.complemento || null,
|
||||
bairro: formData.bairro || null,
|
||||
cidade: formData.cidade || null,
|
||||
estado: formData.estado || null,
|
||||
},
|
||||
observacoes: formData.observacoes || null,
|
||||
convenio: formData.convenio || null,
|
||||
plano: formData.plano || null,
|
||||
numero_matricula: formData.numeroMatricula || null,
|
||||
validade_carteira: formData.validadeCarteira || null,
|
||||
full_name: formData.nome || null,
|
||||
cpf: formData.cpf || null,
|
||||
email: formData.email || null,
|
||||
phone_mobile: formData.phoneMobile || null,
|
||||
birth_date: formData.dataNascimento || null,
|
||||
social_name: formData.nomeSocial || null,
|
||||
sex: formData.sexo || null,
|
||||
blood_type: formData.bloodType || null,
|
||||
weight_kg: formData.weightKg ? Number(formData.weightKg) : null,
|
||||
height_m: formData.heightM ? Number(formData.heightM) : null,
|
||||
street: formData.street || null,
|
||||
number: formData.number || null,
|
||||
complement: formData.complement || null,
|
||||
neighborhood: formData.neighborhood || null,
|
||||
city: formData.city || null,
|
||||
state: formData.state || null,
|
||||
cep: formData.cep || null,
|
||||
};
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error(`Falha ao atualizar (HTTP ${res.status})`);
|
||||
toast({ title: "Sucesso", description: "Paciente atualizado com sucesso" });
|
||||
router.push("/pacientes");
|
||||
} catch (err: any) {
|
||||
toast({ title: "Erro", description: err?.message || "Não foi possível atualizar o paciente" });
|
||||
}
|
||||
};
|
||||
|
||||
// Validate CPF on blur
|
||||
const validateCpf = async (cpf: string) => {
|
||||
if (!cpf) return;
|
||||
try {
|
||||
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ cpf }),
|
||||
await patientsService.update(patientId, payload);
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: "Paciente atualizado com sucesso",
|
||||
variant: "default"
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json?.success === false) {
|
||||
throw new Error(json?.message || "CPF inválido");
|
||||
}
|
||||
if (json?.message) toast({ title: "CPF", description: String(json.message) });
|
||||
router.push("/secretary/pacientes");
|
||||
} catch (err: any) {
|
||||
toast({ title: "CPF inválido", description: err?.message || "Falha na validação de CPF" });
|
||||
console.error("Erro ao atualizar paciente:", err);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: err?.message || "Não foi possível atualizar o paciente",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// CEP lookup on blur
|
||||
const lookupCep = async (cep: string) => {
|
||||
const onlyDigits = cep?.replace(/\D/g, "");
|
||||
if (!onlyDigits) return;
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/utils/cep/${onlyDigits}`);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const d = data?.data || data;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
endereco: d?.logradouro ?? prev.endereco,
|
||||
bairro: d?.bairro ?? prev.bairro,
|
||||
cidade: d?.localidade ?? d?.cidade ?? prev.cidade,
|
||||
estado: d?.uf ?? d?.estado ?? prev.estado,
|
||||
complemento: d?.complemento ?? prev.complemento,
|
||||
}));
|
||||
} catch {}
|
||||
};
|
||||
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<div className="space-y-6">
|
||||
@ -403,8 +265,8 @@ export default function EditarPacientePage() {
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-6">Anexos</h2>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<input ref={anexoInputRef} type="file" className="hidden" onChange={onAnexoSelected} />
|
||||
<Button type="button" variant="outline" onClick={onPickAnexo} disabled={isUploadingAnexo}>
|
||||
<input ref={anexoInputRef} type="file" className="hidden" />
|
||||
<Button type="button" variant="outline" disabled={isUploadingAnexo}>
|
||||
<Paperclip className="w-4 h-4 mr-2" /> {isUploadingAnexo ? "Enviando..." : "Adicionar anexo"}
|
||||
</Button>
|
||||
</div>
|
||||
@ -418,7 +280,7 @@ export default function EditarPacientePage() {
|
||||
<Paperclip className="w-4 h-4 text-gray-500 shrink-0" />
|
||||
<span className="text-sm text-gray-800 truncate">{a.nome || a.filename || `Anexo ${a.id}`}</span>
|
||||
</div>
|
||||
<Button type="button" variant="ghost" className="text-red-600" onClick={() => onDeleteAnexo(a.id)}>
|
||||
<Button type="button" variant="ghost" className="text-red-600">
|
||||
<Trash2 className="w-4 h-4 mr-1" /> Remover
|
||||
</Button>
|
||||
</li>
|
||||
@ -446,12 +308,12 @@ export default function EditarPacientePage() {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input ref={fileInputRef} type="file" accept="image/*" className="hidden" onChange={onPhotoSelected} />
|
||||
<Button type="button" variant="outline" onClick={onPickPhoto} disabled={isUploadingPhoto}>
|
||||
<input ref={fileInputRef} type="file" accept="image/*" className="hidden" />
|
||||
<Button type="button" variant="outline" disabled={isUploadingPhoto}>
|
||||
{isUploadingPhoto ? "Enviando..." : "Enviar foto"}
|
||||
</Button>
|
||||
{photoUrl && (
|
||||
<Button type="button" variant="ghost" onClick={onRemovePhoto} disabled={isUploadingPhoto}>
|
||||
<Button type="button" variant="ghost" disabled={isUploadingPhoto}>
|
||||
Remover
|
||||
</Button>
|
||||
)}
|
||||
@ -465,7 +327,7 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cpf">CPF *</Label>
|
||||
<Input id="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} onBlur={() => validateCpf(formData.cpf)} placeholder="000.000.000-00" required />
|
||||
<Input id="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} placeholder="000.000.000-00" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@ -477,12 +339,12 @@ export default function EditarPacientePage() {
|
||||
<Label>Sexo *</Label>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="radio" id="masculino" name="sexo" value="masculino" checked={formData.sexo === "masculino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||
<Label htmlFor="masculino">Masculino</Label>
|
||||
<input type="radio" id="Masculino" name="sexo" value="Masculino" checked={formData.sexo === "Masculino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||
<Label htmlFor="Masculino">Masculino</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="radio" id="feminino" name="sexo" value="feminino" checked={formData.sexo === "feminino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||
<Label htmlFor="feminino">Feminino</Label>
|
||||
<input type="radio" id="Feminino" name="sexo" value="Feminino" checked={formData.sexo === "Feminino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||
<Label htmlFor="Feminino">Feminino</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -494,7 +356,7 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="etnia">Etnia</Label>
|
||||
<Select value={formData.etnia} onValueChange={(value) => handleInputChange("etnia", value)}>
|
||||
<Select value={formData.ethnicity} onValueChange={(value) => handleInputChange("ethnicity", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -510,7 +372,7 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="raca">Raça</Label>
|
||||
<Select value={formData.raca} onValueChange={(value) => handleInputChange("raca", value)}>
|
||||
<Select value={formData.race} onValueChange={(value) => handleInputChange("race", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -524,12 +386,12 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="naturalidade">Naturalidade</Label>
|
||||
<Input id="naturalidade" value={formData.naturalidade} onChange={(e) => handleInputChange("naturalidade", e.target.value)} />
|
||||
<Input id="naturalidade" value={formData.naturality} onChange={(e) => handleInputChange("naturality", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nacionalidade">Nacionalidade</Label>
|
||||
<Select value={formData.nacionalidade} onValueChange={(value) => handleInputChange("nacionalidade", value)}>
|
||||
<Select value={formData.nationality} onValueChange={(value) => handleInputChange("nationality", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -542,12 +404,12 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="profissao">Profissão</Label>
|
||||
<Input id="profissao" value={formData.profissao} onChange={(e) => handleInputChange("profissao", e.target.value)} />
|
||||
<Input id="profissao" value={formData.profession} onChange={(e) => handleInputChange("profession", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="estadoCivil">Estado civil</Label>
|
||||
<Select value={formData.estadoCivil} onValueChange={(value) => handleInputChange("estadoCivil", value)}>
|
||||
<Select value={formData.maritalStatus} onValueChange={(value) => handleInputChange("maritalStatus", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -562,37 +424,37 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nomeMae">Nome da mãe</Label>
|
||||
<Input id="nomeMae" value={formData.nomeMae} onChange={(e) => handleInputChange("nomeMae", e.target.value)} />
|
||||
<Input id="nomeMae" value={formData.motherName} onChange={(e) => handleInputChange("motherName", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="profissaoMae">Profissão da mãe</Label>
|
||||
<Input id="profissaoMae" value={formData.profissaoMae} onChange={(e) => handleInputChange("profissaoMae", e.target.value)} />
|
||||
<Input id="profissaoMae" value={formData.motherProfession} onChange={(e) => handleInputChange("motherProfession", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nomePai">Nome do pai</Label>
|
||||
<Input id="nomePai" value={formData.nomePai} onChange={(e) => handleInputChange("nomePai", e.target.value)} />
|
||||
<Input id="nomePai" value={formData.fatherName} onChange={(e) => handleInputChange("fatherName", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="profissaoPai">Profissão do pai</Label>
|
||||
<Input id="profissaoPai" value={formData.profissaoPai} onChange={(e) => handleInputChange("profissaoPai", e.target.value)} />
|
||||
<Input id="profissaoPai" value={formData.fatherProfession} onChange={(e) => handleInputChange("fatherProfession", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nomeResponsavel">Nome do responsável</Label>
|
||||
<Input id="nomeResponsavel" value={formData.nomeResponsavel} onChange={(e) => handleInputChange("nomeResponsavel", e.target.value)} />
|
||||
<Input id="nomeResponsavel" value={formData.guardianName} onChange={(e) => handleInputChange("guardianName", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cpfResponsavel">CPF do responsável</Label>
|
||||
<Input id="cpfResponsavel" value={formData.cpfResponsavel} onChange={(e) => handleInputChange("cpfResponsavel", e.target.value)} placeholder="000.000.000-00" />
|
||||
<Input id="cpfResponsavel" value={formData.guardianCpf} onChange={(e) => handleInputChange("guardianCpf", e.target.value)} placeholder="000.000.000-00" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nomeEsposo">Nome do esposo(a)</Label>
|
||||
<Input id="nomeEsposo" value={formData.nomeEsposo} onChange={(e) => handleInputChange("nomeEsposo", e.target.value)} />
|
||||
<Input id="nomeEsposo" value={formData.spouseName} onChange={(e) => handleInputChange("spouseName", e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -605,7 +467,7 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="mt-6">
|
||||
<Label htmlFor="observacoes">Observações</Label>
|
||||
<Textarea id="observacoes" value={formData.observacoes} onChange={(e) => handleInputChange("observacoes", e.target.value)} placeholder="Digite observações sobre o paciente..." className="mt-2" />
|
||||
<Textarea id="observacoes" value={formData.notes} onChange={(e) => handleInputChange("notes", e.target.value)} placeholder="Digite observações sobre o paciente..." className="mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -615,23 +477,23 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">E-mail</Label>
|
||||
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} />
|
||||
<Label htmlFor="email">E-mail *</Label>
|
||||
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} required/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="celular">Celular</Label>
|
||||
<Input id="celular" value={formData.celular} onChange={(e) => handleInputChange("celular", e.target.value)} placeholder="(00) 00000-0000" />
|
||||
<Label htmlFor="celular">Celular *</Label>
|
||||
<Input id="celular" value={formData.phoneMobile} onChange={(e) => handleInputChange("phoneMobile", e.target.value)} placeholder="(00) 00000-0000" required/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="telefone1">Telefone 1</Label>
|
||||
<Input id="telefone1" value={formData.telefone1} onChange={(e) => handleInputChange("telefone1", e.target.value)} placeholder="(00) 0000-0000" />
|
||||
<Input id="telefone1" value={formData.phone1} onChange={(e) => handleInputChange("phone1", e.target.value)} placeholder="(00) 0000-0000" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="telefone2">Telefone 2</Label>
|
||||
<Input id="telefone2" value={formData.telefone2} onChange={(e) => handleInputChange("telefone2", e.target.value)} placeholder="(00) 0000-0000" />
|
||||
<Input id="telefone2" value={formData.phone2} onChange={(e) => handleInputChange("phone2", e.target.value)} placeholder="(00) 0000-0000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -643,37 +505,37 @@ export default function EditarPacientePage() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cep">CEP</Label>
|
||||
<Input id="cep" value={formData.cep} onChange={(e) => handleInputChange("cep", e.target.value)} onBlur={() => lookupCep(formData.cep)} placeholder="00000-000" />
|
||||
<Input id="cep" value={formData.cep} onChange={(e) => handleInputChange("cep", e.target.value)} placeholder="00000-000" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="endereco">Endereço</Label>
|
||||
<Input id="endereco" value={formData.endereco} onChange={(e) => handleInputChange("endereco", e.target.value)} />
|
||||
<Input id="endereco" value={formData.street} onChange={(e) => handleInputChange("street", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="numero">Número</Label>
|
||||
<Input id="numero" value={formData.numero} onChange={(e) => handleInputChange("numero", e.target.value)} />
|
||||
<Input id="numero" value={formData.number} onChange={(e) => handleInputChange("number", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="complemento">Complemento</Label>
|
||||
<Input id="complemento" value={formData.complemento} onChange={(e) => handleInputChange("complemento", e.target.value)} />
|
||||
<Input id="complemento" value={formData.complement} onChange={(e) => handleInputChange("complement", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="bairro">Bairro</Label>
|
||||
<Input id="bairro" value={formData.bairro} onChange={(e) => handleInputChange("bairro", e.target.value)} />
|
||||
<Input id="bairro" value={formData.neighborhood} onChange={(e) => handleInputChange("neighborhood", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cidade">Cidade</Label>
|
||||
<Input id="cidade" value={formData.cidade} onChange={(e) => handleInputChange("cidade", e.target.value)} />
|
||||
<Input id="cidade" value={formData.city} onChange={(e) => handleInputChange("city", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="estado">Estado</Label>
|
||||
<Select value={formData.estado} onValueChange={(value) => handleInputChange("estado", value)}>
|
||||
<Select value={formData.state} onValueChange={(value) => handleInputChange("state", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -718,7 +580,7 @@ export default function EditarPacientePage() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tipoSanguineo">Tipo Sanguíneo</Label>
|
||||
<Select value={formData.tipoSanguineo} onValueChange={(value) => handleInputChange("tipoSanguineo", value)}>
|
||||
<Select value={formData.bloodType} onValueChange={(value) => handleInputChange("bloodType", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -737,23 +599,23 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="peso">Peso (kg)</Label>
|
||||
<Input id="peso" type="number" value={formData.peso} onChange={(e) => handleInputChange("peso", e.target.value)} placeholder="0.0" />
|
||||
<Input id="peso" type="number" value={formData.weightKg} onChange={(e) => handleInputChange("weightKg", e.target.value)} placeholder="0.0" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="altura">Altura (m)</Label>
|
||||
<Input id="altura" type="number" step="0.01" value={formData.altura} onChange={(e) => handleInputChange("altura", e.target.value)} placeholder="0.00" />
|
||||
<Input id="altura" type="number" step="0.01" value={formData.heightM} onChange={(e) => handleInputChange("heightM", e.target.value)} placeholder="0.00" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>IMC</Label>
|
||||
<Input value={formData.peso && formData.altura ? (Number.parseFloat(formData.peso) / Number.parseFloat(formData.altura) ** 2).toFixed(2) : ""} disabled placeholder="Calculado automaticamente" />
|
||||
<Input value={formData.weightKg && formData.heightM ? (Number.parseFloat(formData.weightKg) / Number.parseFloat(formData.heightM) ** 2).toFixed(2) : ""} disabled placeholder="Calculado automaticamente" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<Label htmlFor="alergias">Alergias</Label>
|
||||
<Textarea id="alergias" value={formData.alergias} onChange={(e) => handleInputChange("alergias", e.target.value)} placeholder="Ex: AAS, Dipirona, etc." className="mt-2" />
|
||||
<Textarea id="alergias" onChange={(e) => handleInputChange("alergias", e.target.value)} placeholder="Ex: AAS, Dipirona, etc." className="mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -764,7 +626,7 @@ export default function EditarPacientePage() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="convenio">Convênio</Label>
|
||||
<Select value={formData.convenio} onValueChange={(value) => handleInputChange("convenio", value)}>
|
||||
<Select onValueChange={(value) => handleInputChange("convenio", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
@ -780,17 +642,17 @@ export default function EditarPacientePage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="plano">Plano</Label>
|
||||
<Input id="plano" value={formData.plano} onChange={(e) => handleInputChange("plano", e.target.value)} />
|
||||
<Input id="plano" onChange={(e) => handleInputChange("plano", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="numeroMatricula">Nº de matrícula</Label>
|
||||
<Input id="numeroMatricula" value={formData.numeroMatricula} onChange={(e) => handleInputChange("numeroMatricula", e.target.value)} />
|
||||
<Input id="numeroMatricula" onChange={(e) => handleInputChange("numeroMatricula", e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="validadeCarteira">Validade da Carteira</Label>
|
||||
<Input id="validadeCarteira" type="date" value={formData.validadeCarteira} onChange={(e) => handleInputChange("validadeCarteira", e.target.value)} disabled={validadeIndeterminada} />
|
||||
<Input id="validadeCarteira" type="date" onChange={(e) => handleInputChange("validadeCarteira", e.target.value)} disabled={validadeIndeterminada} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -803,7 +665,7 @@ export default function EditarPacientePage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Link href="/pacientes">
|
||||
<Link href="/secretary/pacientes">
|
||||
<Button type="button" variant="outline">
|
||||
Cancelar
|
||||
</Button>
|
||||
|
||||
@ -15,6 +15,7 @@ import { Upload, Plus, X, ChevronDown } from "lucide-react";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
|
||||
export default function NovoPacientePage() {
|
||||
const [anexosOpen, setAnexosOpen] = useState(false);
|
||||
@ -31,111 +32,111 @@ export default function NovoPacientePage() {
|
||||
setAnexos(anexos.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
|
||||
const cleanNumber = (value: string): string => value.replace(/\D/g, '');
|
||||
|
||||
const formatCPF = (value: string): string => {
|
||||
const cleaned = cleanNumber(value).substring(0, 11);
|
||||
return cleaned.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
|
||||
};
|
||||
|
||||
const formatCEP = (value: string): string => {
|
||||
const cleaned = cleanNumber(value).substring(0, 8);
|
||||
return cleaned.replace(/(\d{5})(\d{3})/, '$1-$2');
|
||||
};
|
||||
|
||||
const formatPhoneMobile = (value: string): string => {
|
||||
const cleaned = cleanNumber(value).substring(0, 11);
|
||||
if (cleaned.length > 10) {
|
||||
return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, '+55 ($1) $2-$3');
|
||||
}
|
||||
return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '+55 ($1) $2-$3');
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
|
||||
const apiPayload = {
|
||||
nome: formData.get("nome") as string,
|
||||
nome_social: (formData.get("nomeSocial") as string) || null,
|
||||
cpf: formData.get("cpf") as string,
|
||||
rg: (formData.get("rg") as string) || null,
|
||||
outros_documentos:
|
||||
(formData.get("outrosDocumentosTipo") as string) || (formData.get("outrosDocumentosNumero") as string)
|
||||
? {
|
||||
tipo: (formData.get("outrosDocumentosTipo") as string) || undefined,
|
||||
numero: (formData.get("outrosDocumentosNumero") as string) || undefined,
|
||||
}
|
||||
: null,
|
||||
sexo: (formData.get("sexo") as string) || null,
|
||||
data_nascimento: (formData.get("dataNascimento") as string) || null,
|
||||
etnia: (formData.get("etnia") as string) || null,
|
||||
raca: (formData.get("raca") as string) || null,
|
||||
naturalidade: (formData.get("naturalidade") as string) || null,
|
||||
nacionalidade: (formData.get("nacionalidade") as string) || null,
|
||||
profissao: (formData.get("profissao") as string) || null,
|
||||
estado_civil: (formData.get("estadoCivil") as string) || null,
|
||||
nome_mae: (formData.get("nomeMae") as string) || null,
|
||||
profissao_mae: (formData.get("profissaoMae") as string) || null,
|
||||
nome_pai: (formData.get("nomePai") as string) || null,
|
||||
profissao_pai: (formData.get("profissaoPai") as string) || null,
|
||||
nome_responsavel: (formData.get("nomeResponsavel") as string) || null,
|
||||
cpf_responsavel: (formData.get("cpfResponsavel") as string) || null,
|
||||
nome_esposo: (formData.get("nomeEsposo") as string) || null,
|
||||
rn_na_guia_convenio: Boolean(formData.get("rnGuia")),
|
||||
codigo_legado: (formData.get("codigoLegado") as string) || null,
|
||||
contato: {
|
||||
email: (formData.get("email") as string) || null,
|
||||
celular: (formData.get("celular") as string) || null,
|
||||
telefone1: (formData.get("telefone1") as string) || null,
|
||||
telefone2: (formData.get("telefone2") as string) || null,
|
||||
},
|
||||
endereco: {
|
||||
cep: (formData.get("cep") as string) || null,
|
||||
logradouro: (formData.get("endereco") as string) || null,
|
||||
numero: (formData.get("numero") as string) || null,
|
||||
complemento: (formData.get("complemento") as string) || null,
|
||||
bairro: (formData.get("bairro") as string) || null,
|
||||
cidade: (formData.get("cidade") as string) || null,
|
||||
estado: (formData.get("estado") as string) || null,
|
||||
referencia: null,
|
||||
},
|
||||
observacoes: (formData.get("observacoes") as string) || null,
|
||||
// Campos de convênio (opcionais, se a API aceitar)
|
||||
convenio: (formData.get("convenio") as string) || null,
|
||||
plano: (formData.get("plano") as string) || null,
|
||||
numero_matricula: (formData.get("numeroMatricula") as string) || null,
|
||||
validade_carteira: (formData.get("validadeCarteira") as string) || null,
|
||||
full_name: (formData.get("nome") as string) || "", // obrigatório
|
||||
social_name: (formData.get("nomeSocial") as string) || undefined,
|
||||
cpf: (formatCPF(formData.get("cpf") as string)) || "", // obrigatório
|
||||
email: (formData.get("email") as string) || "", // obrigatório
|
||||
phone_mobile: (formatPhoneMobile(formData.get("celular") as string)) || "", // obrigatório
|
||||
birth_date: formData.get("dataNascimento") ? new Date(formData.get("dataNascimento") as string) : undefined,
|
||||
sex: (formData.get("sexo") as string) || undefined,
|
||||
blood_type: (formData.get("tipoSanguineo") as string) || undefined,
|
||||
weight_kg: formData.get("peso") ? parseFloat(formData.get("peso") as string) : undefined,
|
||||
height_m: formData.get("altura") ? parseFloat(formData.get("altura") as string) : undefined,
|
||||
cep: (formatCEP(formData.get("cep") as string)) || undefined,
|
||||
street: (formData.get("endereco") as string) || undefined,
|
||||
number: (formData.get("numero") as string) || undefined,
|
||||
complement: (formData.get("complemento") as string) || undefined,
|
||||
neighborhood: (formData.get("bairro") as string) || undefined,
|
||||
city: (formData.get("cidade") as string) || undefined,
|
||||
state: (formData.get("estado") as string) || undefined,
|
||||
};
|
||||
|
||||
const errors: string[] = [];
|
||||
const nome = apiPayload.nome?.trim() || "";
|
||||
if (!nome || nome.length < 2 || nome.length > 255) errors.push("Nome deve ter entre 2 e 255 caracteres.");
|
||||
const cpf = apiPayload.cpf || "";
|
||||
if (!/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(cpf)) errors.push("CPF deve estar no formato XXX.XXX.XXX-XX.");
|
||||
const sexo = apiPayload.sexo;
|
||||
const allowedSexo = ["masculino", "feminino", "outro"];
|
||||
if (!sexo || !allowedSexo.includes(sexo)) errors.push("Sexo é obrigatório e deve ser masculino, feminino ou outro.");
|
||||
if (!apiPayload.data_nascimento) errors.push("Data de nascimento é obrigatória.");
|
||||
const celular = apiPayload.contato?.celular || "";
|
||||
if (celular && !/^\+55 \(\d{2}\) \d{4,5}-\d{4}$/.test(celular)) errors.push("Celular deve estar no formato +55 (XX) XXXXX-XXXX.");
|
||||
const cep = apiPayload.endereco?.cep || "";
|
||||
if (cep && !/^\d{5}-\d{3}$/.test(cep)) errors.push("CEP deve estar no formato XXXXX-XXX.");
|
||||
const uf = apiPayload.endereco?.estado || "";
|
||||
if (uf && uf.length !== 2) errors.push("Estado (UF) deve ter 2 caracteres.");
|
||||
console.log(apiPayload.email)
|
||||
console.log(apiPayload.cep)
|
||||
console.log(apiPayload.phone_mobile)
|
||||
|
||||
const errors: string[] = [];
|
||||
const fullName = apiPayload.full_name?.trim() || "";
|
||||
if (!fullName || fullName.length < 2 || fullName.length > 255) {
|
||||
errors.push("Nome deve ter entre 2 e 255 caracteres.");
|
||||
}
|
||||
|
||||
const cpf = apiPayload.cpf || "";
|
||||
if (!/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(cpf)) {
|
||||
errors.push("CPF deve estar no formato XXX.XXX.XXX-XX.");
|
||||
}
|
||||
|
||||
const sex = apiPayload.sex;
|
||||
const allowedSex = ["Masculino", "Feminino", "outro"];
|
||||
if (!sex || !allowedSex.includes(sex)) {
|
||||
errors.push("Sexo é obrigatório e deve ser masculino, feminino ou outro.");
|
||||
}
|
||||
|
||||
if (!apiPayload.birth_date) {
|
||||
errors.push("Data de nascimento é obrigatória.");
|
||||
}
|
||||
|
||||
const phoneMobile = apiPayload.phone_mobile || "";
|
||||
if (phoneMobile && !/^\+55 \(\d{2}\) \d{4,5}-\d{4}$/.test(phoneMobile)) {
|
||||
errors.push("Celular deve estar no formato +55 (XX) XXXXX-XXXX.");
|
||||
}
|
||||
|
||||
const cep = apiPayload.cep || "";
|
||||
if (cep && !/^\d{5}-\d{3}$/.test(cep)) {
|
||||
errors.push("CEP deve estar no formato XXXXX-XXX.");
|
||||
}
|
||||
|
||||
const state = apiPayload.state || "";
|
||||
if (state && state.length !== 2) {
|
||||
errors.push("Estado (UF) deve ter 2 caracteres.");
|
||||
}
|
||||
if (errors.length) {
|
||||
toast({ title: "Corrija os campos", description: errors[0] });
|
||||
console.log("campos errados")
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(apiPayload),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const msg = `Erro ao salvar (HTTP ${res.status})`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
const res = await patientsService.create(apiPayload);
|
||||
console.log(res)
|
||||
|
||||
let message = "Paciente cadastrado com sucesso";
|
||||
try {
|
||||
const payload = await res.json();
|
||||
if (payload?.success === false) {
|
||||
throw new Error(payload?.message || "A API retornou erro");
|
||||
if (!res[0].id) {
|
||||
throw new Error(`${res.error} ${res.message}`|| "A API retornou erro");
|
||||
} else {
|
||||
console.log(message)
|
||||
}
|
||||
if (payload?.message) message = String(payload.message);
|
||||
} catch {}
|
||||
|
||||
toast({
|
||||
@ -225,23 +226,23 @@ export default function NovoPacientePage() {
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-gray-700">Sexo</Label>
|
||||
<Label className="text-sm font-medium text-gray-700">Sexo *</Label>
|
||||
<div className="flex gap-4 mt-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="radio" name="sexo" value="masculino" className="text-blue-600" />
|
||||
<input type="radio" name="sexo" value="Masculino" className="text-blue-600" required/>
|
||||
<span className="text-sm">Masculino</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="radio" name="sexo" value="feminino" className="text-blue-600" />
|
||||
<input type="radio" name="sexo" value="Feminino" className="text-blue-600" required/>
|
||||
<span className="text-sm">Feminino</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="dataNascimento" className="text-sm font-medium text-gray-700">
|
||||
Data de Nascimento
|
||||
Data de Nascimento *
|
||||
</Label>
|
||||
<Input id="dataNascimento" name="dataNascimento" type="date" className="mt-1" />
|
||||
<Input id="dataNascimento" name="dataNascimento" type="date" className="mt-1" required/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="estadoCivil" className="text-sm font-medium text-gray-700">
|
||||
@ -447,13 +448,13 @@ export default function NovoPacientePage() {
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="email" className="text-sm font-medium text-gray-700">
|
||||
E-mail
|
||||
E-mail *
|
||||
</Label>
|
||||
<Input id="email" name="email" type="email" placeholder="email@exemplo.com" className="mt-1" />
|
||||
<Input id="email" name="email" type="email" placeholder="email@exemplo.com" className="mt-1" required/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="celular" className="text-sm font-medium text-gray-700">
|
||||
Celular
|
||||
Celular *
|
||||
</Label>
|
||||
<div className="flex mt-1">
|
||||
<Select>
|
||||
@ -464,7 +465,7 @@ export default function NovoPacientePage() {
|
||||
<SelectItem value="+55">+55</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input name="celular" placeholder="(XX) XXXXX-XXXX" className="rounded-l-none" />
|
||||
<Input id="celular" name="celular" placeholder="(XX) XXXXX-XXXX" className="rounded-l-none" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -8,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter } from "lucide-react";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import { patientsService } from "@/services/patientsApi.mjs"
|
||||
|
||||
export default function PacientesPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
@ -28,40 +29,41 @@ export default function PacientesPage() {
|
||||
setDetailsDialogOpen(true);
|
||||
setPatientDetails(null);
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json = await res.json();
|
||||
setPatientDetails(json?.data ?? null);
|
||||
const res = await patientsService.getById(patientId);
|
||||
setPatientDetails(res[0]);
|
||||
} catch (e: any) {
|
||||
setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchPacientes = useCallback(
|
||||
async (pageToFetch: number) => {
|
||||
if (isFetching || !hasNext) return;
|
||||
setIsFetching(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes?page=${pageToFetch}&limit=20`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json = await res.json();
|
||||
const items = Array.isArray(json?.data) ? json.data : [];
|
||||
const mapped = items.map((p: any) => ({
|
||||
id: String(p.id ?? ""),
|
||||
nome: p.nome ?? "",
|
||||
telefone: p?.contato?.celular ?? p?.contato?.telefone1 ?? p?.telefone ?? "",
|
||||
cidade: p?.endereco?.cidade ?? p?.cidade ?? "",
|
||||
estado: p?.endereco?.estado ?? p?.estado ?? "",
|
||||
ultimoAtendimento: p.ultimo_atendimento ?? p.ultimoAtendimento ?? undefined,
|
||||
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? undefined,
|
||||
convenio: p.convenio ?? "",
|
||||
vip: Boolean(p.vip ?? false),
|
||||
status: p.status ?? undefined,
|
||||
}));
|
||||
setPatients((prev) => [...prev, ...mapped]);
|
||||
setHasNext(Boolean(json?.pagination?.has_next));
|
||||
setPage(pageToFetch + 1);
|
||||
const res = await patientsService.list();
|
||||
const mapped = res.map((p: any) => ({
|
||||
id: String(p.id ?? ""),
|
||||
nome: p.full_name ?? "",
|
||||
telefone: p.phone_mobile ?? p.phone1 ?? "",
|
||||
cidade: p.city ?? "",
|
||||
estado: p.state ?? "",
|
||||
ultimoAtendimento: p.last_visit_at ?? "",
|
||||
proximoAtendimento: p.next_appointment_at ?? "",
|
||||
vip: Boolean(p.vip ?? false),
|
||||
convenio: p.convenio ?? "", // se não existir, fica vazio
|
||||
status: p.status ?? undefined,
|
||||
}));
|
||||
|
||||
setPatients((prev) => {
|
||||
const all = [...prev, ...mapped];
|
||||
const unique = Array.from(new Map(all.map(p => [p.id, p])).values());
|
||||
return unique;
|
||||
});
|
||||
|
||||
if (mapped.length === 0) setHasNext(false); // parar carregamento
|
||||
else setPage(prev => prev + 1);
|
||||
} catch (e: any) {
|
||||
setError(e?.message || "Erro ao buscar pacientes");
|
||||
} finally {
|
||||
@ -89,9 +91,21 @@ export default function PacientesPage() {
|
||||
};
|
||||
}, [fetchPacientes, page, hasNext, isFetching]);
|
||||
|
||||
const handleDeletePatient = (patientId: string) => {
|
||||
const handleDeletePatient = async (patientId: string) => {
|
||||
// Remove from current list (client-side deletion)
|
||||
setPatients((prev) => prev.filter((p) => String(p.id) !== String(patientId)));
|
||||
try{
|
||||
const res = await patientsService.delete(patientId);
|
||||
|
||||
if(res){
|
||||
alert(`${res.error} ${res.message}`)
|
||||
}
|
||||
|
||||
setPatients((prev) => prev.filter((p) => String(p.id) !== String(patientId)));
|
||||
|
||||
|
||||
} catch (e: any) {
|
||||
setError(e?.message || "Erro ao deletar paciente");
|
||||
}
|
||||
setDeleteDialogOpen(false);
|
||||
setPatientToDelete(null);
|
||||
};
|
||||
@ -311,33 +325,24 @@ export default function PacientesPage() {
|
||||
<div className="text-red-600">{patientDetails.error}</div>
|
||||
) : (
|
||||
<div className="space-y-2 text-left">
|
||||
<div>
|
||||
<strong>Nome:</strong> {patientDetails.nome}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Telefone:</strong> {patientDetails?.contato?.celular ?? patientDetails?.contato?.telefone1 ?? patientDetails?.telefone ?? ""}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Cidade:</strong> {patientDetails?.endereco?.cidade ?? patientDetails?.cidade ?? ""}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Estado:</strong> {patientDetails?.endereco?.estado ?? patientDetails?.estado ?? ""}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Convênio:</strong> {patientDetails.convenio ?? ""}
|
||||
</div>
|
||||
<div>
|
||||
<strong>VIP:</strong> {patientDetails.vip ? "Sim" : "Não"}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Status:</strong> {patientDetails.status ?? ""}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Último atendimento:</strong> {patientDetails.ultimo_atendimento ?? patientDetails.ultimoAtendimento ?? ""}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Próximo atendimento:</strong> {patientDetails.proximo_atendimento ?? patientDetails.proximoAtendimento ?? ""}
|
||||
</div>
|
||||
<p><strong>Nome:</strong> {patientDetails.full_name}</p>
|
||||
<p><strong>CPF:</strong> {patientDetails.cpf}</p>
|
||||
<p><strong>Email:</strong> {patientDetails.email}</p>
|
||||
<p><strong>Telefone:</strong> {patientDetails.phone_mobile ?? patientDetails.phone1 ?? patientDetails.phone2 ?? "-"}</p>
|
||||
<p><strong>Nome social:</strong> {patientDetails.social_name ?? "-"}</p>
|
||||
<p><strong>Sexo:</strong> {patientDetails.sex ?? "-"}</p>
|
||||
<p><strong>Tipo sanguíneo:</strong> {patientDetails.blood_type ?? "-"}</p>
|
||||
<p><strong>Peso:</strong> {patientDetails.weight_kg ?? "-"}{patientDetails.weight_kg ? "kg": ""}</p>
|
||||
<p><strong>Altura:</strong> {patientDetails.height_m ?? "-"}{patientDetails.height_m ? "m": ""}</p>
|
||||
<p><strong>IMC:</strong> {patientDetails.bmi ?? "-"}</p>
|
||||
<p><strong>Endereço:</strong> {patientDetails.street ?? "-"}</p>
|
||||
<p><strong>Bairro:</strong> {patientDetails.neighborhood ?? "-"}</p>
|
||||
<p><strong>Cidade:</strong> {patientDetails.city ?? "-"}</p>
|
||||
<p><strong>Estado:</strong> {patientDetails.state ?? "-"}</p>
|
||||
<p><strong>CEP:</strong> {patientDetails.cep ?? "-"}</p>
|
||||
<p><strong>Criado em:</strong> {patientDetails.created_at ?? "-"}</p>
|
||||
<p><strong>Atualizado em:</strong> {patientDetails.updated_at ?? "-"}</p>
|
||||
<p><strong>Id:</strong> {patientDetails.id ?? "-"}</p>
|
||||
</div>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
|
||||
@ -66,13 +66,13 @@ export default function SecretaryLayout({ children }: PatientLayoutProps) {
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href: "#",
|
||||
href: "##",
|
||||
icon: Home,
|
||||
label: "Dashboard",
|
||||
// Botão para o dashboard da secretária
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
href: "###",
|
||||
icon: Calendar,
|
||||
label: "Consultas",
|
||||
// Botão para página de consultas marcadas
|
||||
|
||||
@ -4,36 +4,38 @@ const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
||||
const API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
||||
var tempToken;
|
||||
|
||||
async function login() {
|
||||
export async function login() {
|
||||
const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Prefer: "return=representation",
|
||||
"apikey": API_KEY, // valor fixo
|
||||
},
|
||||
body: JSON.stringify({ email: "hugo@popcode.com.br", password: "hdoria" }),
|
||||
body: JSON.stringify({ email: "riseup@popcode.com.br", password: "riseup" }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
console.log("Resposta da API:", data);
|
||||
console.log("Token:", data.access_token);
|
||||
// salvar o token do usuário
|
||||
//localStorage.setItem("token", data.access_token);
|
||||
tempToken = data.access_token
|
||||
localStorage.setItem("token", data.access_token);
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
await login()
|
||||
|
||||
async function run(){
|
||||
await login()
|
||||
}
|
||||
run()
|
||||
|
||||
async function request(endpoint, options = {}) {
|
||||
//const token = localStorage.getItem("token"); // token do usuário, salvo no login
|
||||
const token = tempToken;
|
||||
const token = localStorage.getItem("token"); // token do usuário, salvo no login
|
||||
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"apikey": API_KEY, // obrigatório sempre
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}), // obrigatório em todas EXCETO login
|
||||
...(token ? { "Authorization": `Bearer ${token}` } : {}), // obrigatório em todas EXCETO login
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
@ -44,10 +46,19 @@ async function request(endpoint, options = {}) {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.error("Erro HTTP:", response.status, text);
|
||||
throw new Error(`Erro HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
// Lê a resposta como texto
|
||||
const text = await response.text();
|
||||
|
||||
// Se não tiver conteúdo (204 ou 201 sem body), retorna null
|
||||
if (!text) return null;
|
||||
|
||||
// Se tiver conteúdo, parseia como JSON
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.error("Erro na requisição:", error);
|
||||
throw error;
|
||||
|
||||
@ -2,8 +2,8 @@ import { api } from "./api.mjs";
|
||||
|
||||
export const patientsService = {
|
||||
list: () => api.get("/rest/v1/patients"),
|
||||
getById: (id) => api.get(`/rest/v1/patients/${id}`),
|
||||
getById: (id) => api.get(`/rest/v1/patients?id=eq.${id}`),
|
||||
create: (data) => api.post("/rest/v1/patients", data),
|
||||
update: (id, data) => api.patch(`/rest/v1/patients/${id}`, data),
|
||||
delete: (id) => api.delete(`/rest/v1/patients/${id}`),
|
||||
update: (id, data) => api.patch(`/rest/v1/patients?id=eq.${id}`, data),
|
||||
delete: (id) => api.delete(`/rest/v1/patients?id=eq.${id}`),
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user