Merge pull request 'troca-de-API' (#9) from StsDanilo/riseup-squad21:troca-de-API into troca-de-API

Reviewed-on: RiseUP/riseup-squad21#9
This commit is contained in:
lucas deiró 2025-09-29 20:03:33 +00:00
commit cb598058d6
18 changed files with 2501 additions and 188 deletions

56
app/finance/home/page.tsx Normal file
View File

@ -0,0 +1,56 @@
"use client";
import { useEffect, useState } from "react";
import FinancierLayout from "@/components/finance-layout";
interface Paciente {
id: string;
nome: string;
telefone: string;
cidade: string;
estado: string;
ultimoAtendimento?: string;
proximoAtendimento?: string;
}
export default function PacientesPage() {
const [pacientes, setPacientes] = useState<Paciente[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchPacientes() {
try {
setLoading(true);
setError(null);
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes");
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 ?? "",
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? "",
}));
setPacientes(mapped);
} catch (e: any) {
setError(e?.message || "Erro ao carregar pacientes");
} finally {
setLoading(false);
}
}
fetchPacientes();
}, []);
return (
<FinancierLayout>
<div></div>
</FinancierLayout>
);
}

155
app/finance/login/page.tsx Normal file
View File

@ -0,0 +1,155 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import { useToast } from "@/hooks/use-toast"
import { Eye, EyeOff, Mail, Lock, Stethoscope, Loader2, Receipt } from "lucide-react"
import Link from "next/link"
interface LoginForm {
email: string
password: string
}
export default function DoctorLogin() {
const [form, setForm] = useState<LoginForm>({ email: "", password: "" })
const [showPassword, setShowPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const { toast } = useToast()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
// Simular autenticação
setTimeout(() => {
if (form.email && form.password) {
const financierData = {
id: "1",
name: "Thiago Nigro",
email: form.email,
phone: "(11) 98888-8888",
cpf: "987.654.321-00",
department: "Financeiro",
permissions: ["view_reports", "manage_finances", "create_reports"],
}
localStorage.setItem("financierData", JSON.stringify(financierData))
localStorage.setItem("userType", "financier")
toast({
title: "Login realizado com sucesso!",
description: "Bem-vindo ao sistema, " + financierData.name,
})
router.push("/finance/home")
} else {
toast({
title: "Erro no login",
description: "Por favor, preencha todos os campos.",
variant: "destructive",
})
}
setIsLoading(false)
}, 1500)
}
return (
<div className="min-h-screen bg-gradient-to-br from-orange-50 via-white to-orange-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md shadow-xl border-0 bg-white/80 backdrop-blur-sm">
<CardHeader className="text-center space-y-4 pb-8">
<div className="mx-auto w-16 h-16 bg-orange-200 rounded-full flex items-center justify-center">
<Receipt className="w-8 h-8 text-orange-600" />
</div>
<div>
<CardTitle className="text-2xl font-bold text-gray-900">Área do Médico</CardTitle>
<CardDescription className="text-gray-600 mt-2">Acesse o sistema médico</CardDescription>
</div>
</CardHeader>
<CardContent className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="email" className="text-sm font-medium text-gray-700">
E-mail
</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="email"
type="email"
placeholder="dr.medico@clinica.com"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
className="pl-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500"
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-sm font-medium text-gray-700">
Senha
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Digite sua senha"
value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })}
className="pl-10 pr-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<Button
type="submit"
className="w-full h-11 bg-orange-600 hover:bg-orange-700 text-white font-medium"
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Entrando...
</>
) : (
"Entrar"
)}
</Button>
</form>
<div className="relative">
<Separator className="my-6" />
<span className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white px-3 text-sm text-gray-500">
ou
</span>
</div>
<div className="text-center">
<Link href="/" className="text-sm text-orange-600 hover:text-orange-700 font-medium hover:underline">
Voltar à página inicial
</Link>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@ -0,0 +1,686 @@
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { useRouter, useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { Checkbox } from "@/components/ui/checkbox"
import { ArrowLeft, Save } from "lucide-react"
import Link from "next/link"
import ManagerLayout from "@/components/manager-layout"
// Mock data - in a real app, this would come from an API
const mockDoctors = [
{
id: 1,
nome: "Dr. Carlos Silva",
cpf: "123.456.789-00",
rg: "12.345.678-9",
sexo: "masculino",
dataNascimento: "1980-05-15",
etnia: "branca",
raca: "caucasiana",
naturalidade: "Aracaju",
nacionalidade: "brasileira",
profissao: "Médico",
estadoCivil: "casado",
nomeMae: "Ana Silva",
nomePai: "José Silva",
nomeEsposo: "Maria Silva",
crm: "CRM/SE 12345",
especialidade: "Cardiologia",
email: "carlos@email.com",
celular: "(79) 99999-1234",
telefone1: "(79) 3214-5678",
telefone2: "",
cep: "49000-000",
endereco: "Rua dos Médicos, 123",
numero: "123",
complemento: "Sala 101",
bairro: "Centro",
cidade: "Aracaju",
estado: "SE",
tipoSanguineo: "A+",
peso: "80",
altura: "1.80",
alergias: "Nenhuma alergia conhecida",
convenio: "Particular",
plano: "Premium",
numeroMatricula: "123456789",
validadeCarteira: "2025-12-31",
observacoes: "Médico experiente",
},
]
export default function EditarMedicoPage() {
const router = useRouter()
const params = useParams()
const doctorId = Number.parseInt(params.id as string)
const [formData, setFormData] = useState({
nome: "",
cpf: "",
rg: "",
sexo: "",
dataNascimento: "",
etnia: "",
raca: "",
naturalidade: "",
nacionalidade: "",
profissao: "",
estadoCivil: "",
nomeMae: "",
nomePai: "",
nomeEsposo: "",
crm: "",
especialidade: "",
email: "",
celular: "",
telefone1: "",
telefone2: "",
cep: "",
endereco: "",
numero: "",
complemento: "",
bairro: "",
cidade: "",
estado: "",
tipoSanguineo: "",
peso: "",
altura: "",
alergias: "",
convenio: "",
plano: "",
numeroMatricula: "",
validadeCarteira: "",
observacoes: "",
})
const [isGuiaConvenio, setIsGuiaConvenio] = useState(false)
const [validadeIndeterminada, setValidadeIndeterminada] = useState(false)
useEffect(() => {
// Load doctor data
const doctor = mockDoctors.find((d) => d.id === doctorId)
if (doctor) {
setFormData(doctor)
}
}, [doctorId])
const handleInputChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
console.log("[v0] Updating doctor:", formData)
// Here you would typically send the data to your API
router.push("/medicos")
}
return (
<ManagerLayout>
<div className="space-y-6">
<div className="flex items-center gap-4">
<Link href="/manager/home">
<Button variant="ghost" size="sm">
<ArrowLeft className="w-4 h-4 mr-2" />
Voltar
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-gray-900">Editar Médico</h1>
<p className="text-gray-600">Atualize as informações do médico</p>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-8">
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados Pessoais</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="space-y-2">
<Label htmlFor="nome">Nome *</Label>
<Input
id="nome"
value={formData.nome}
onChange={(e) => handleInputChange("nome", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="cpf">CPF *</Label>
<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">
<Label htmlFor="rg">RG</Label>
<Input
id="rg"
value={formData.rg}
onChange={(e) => handleInputChange("rg", e.target.value)}
placeholder="00.000.000-0"
/>
</div>
<div className="space-y-2">
<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>
</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>
</div>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="dataNascimento">Data de nascimento *</Label>
<Input
id="dataNascimento"
type="date"
value={formData.dataNascimento}
onChange={(e) => handleInputChange("dataNascimento", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="etnia">Etnia</Label>
<Select value={formData.etnia} onValueChange={(value) => handleInputChange("etnia", value)}>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="branca">Branca</SelectItem>
<SelectItem value="preta">Preta</SelectItem>
<SelectItem value="parda">Parda</SelectItem>
<SelectItem value="amarela">Amarela</SelectItem>
<SelectItem value="indigena">Indígena</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="raca">Raça</Label>
<Select value={formData.raca} onValueChange={(value) => handleInputChange("raca", value)}>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="caucasiana">Caucasiana</SelectItem>
<SelectItem value="negroide">Negroide</SelectItem>
<SelectItem value="mongoloide">Mongoloide</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="naturalidade">Naturalidade</Label>
<Input
id="naturalidade"
value={formData.naturalidade}
onChange={(e) => handleInputChange("naturalidade", e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="nacionalidade">Nacionalidade</Label>
<Select
value={formData.nacionalidade}
onValueChange={(value) => handleInputChange("nacionalidade", value)}
>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="brasileira">Brasileira</SelectItem>
<SelectItem value="estrangeira">Estrangeira</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="profissao">Profissão</Label>
<Input
id="profissao"
value={formData.profissao}
onChange={(e) => handleInputChange("profissao", e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="estadoCivil">Estado civil</Label>
<Select value={formData.estadoCivil} onValueChange={(value) => handleInputChange("estadoCivil", value)}>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="solteiro">Solteiro(a)</SelectItem>
<SelectItem value="casado">Casado(a)</SelectItem>
<SelectItem value="divorciado">Divorciado(a)</SelectItem>
<SelectItem value="viuvo">Viúvo(a)</SelectItem>
</SelectContent>
</Select>
</div>
<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)}
/>
</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)}
/>
</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)}
/>
</div>
</div>
<div className="mt-6">
<div className="flex items-center space-x-2">
<Checkbox
id="guiaConvenio"
checked={isGuiaConvenio}
onCheckedChange={(checked) => setIsGuiaConvenio(checked === true)}
/>
<Label htmlFor="guiaConvenio">RN na Guia do convênio</Label>
</div>
</div>
<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 médico..."
className="mt-2"
/>
</div>
</div>
{/* Professional Information Section */}
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações Profissionais</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="crm">CRM *</Label>
<Input
id="crm"
value={formData.crm}
onChange={(e) => handleInputChange("crm", e.target.value)}
placeholder="CRM/UF 12345"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="especialidade">Especialidade *</Label>
<Select
value={formData.especialidade}
onValueChange={(value) => handleInputChange("especialidade", value)}
>
<SelectTrigger>
<SelectValue placeholder="Selecione a especialidade" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Cardiologia">Cardiologia</SelectItem>
<SelectItem value="Pediatria">Pediatria</SelectItem>
<SelectItem value="Ortopedia">Ortopedia</SelectItem>
<SelectItem value="Neurologia">Neurologia</SelectItem>
<SelectItem value="Ginecologia">Ginecologia</SelectItem>
<SelectItem value="Dermatologia">Dermatologia</SelectItem>
<SelectItem value="Psiquiatria">Psiquiatria</SelectItem>
<SelectItem value="Oftalmologia">Oftalmologia</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{/* Contact Section */}
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Contato</h2>
<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)}
/>
</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"
/>
</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"
/>
</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"
/>
</div>
</div>
</div>
{/* Address Section */}
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Endereço</h2>
<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)}
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)}
/>
</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)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="complemento">Complemento</Label>
<Input
id="complemento"
value={formData.complemento}
onChange={(e) => handleInputChange("complemento", 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)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="cidade">Cidade</Label>
<Input
id="cidade"
value={formData.cidade}
onChange={(e) => handleInputChange("cidade", e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="estado">Estado</Label>
<Select value={formData.estado} onValueChange={(value) => handleInputChange("estado", value)}>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="AC">Acre</SelectItem>
<SelectItem value="AL">Alagoas</SelectItem>
<SelectItem value="AP">Amapá</SelectItem>
<SelectItem value="AM">Amazonas</SelectItem>
<SelectItem value="BA">Bahia</SelectItem>
<SelectItem value="CE">Ceará</SelectItem>
<SelectItem value="DF">Distrito Federal</SelectItem>
<SelectItem value="ES">Espírito Santo</SelectItem>
<SelectItem value="GO">Goiás</SelectItem>
<SelectItem value="MA">Maranhão</SelectItem>
<SelectItem value="MT">Mato Grosso</SelectItem>
<SelectItem value="MS">Mato Grosso do Sul</SelectItem>
<SelectItem value="MG">Minas Gerais</SelectItem>
<SelectItem value="PA">Pará</SelectItem>
<SelectItem value="PB">Paraíba</SelectItem>
<SelectItem value="PR">Paraná</SelectItem>
<SelectItem value="PE">Pernambuco</SelectItem>
<SelectItem value="PI">Piauí</SelectItem>
<SelectItem value="RJ">Rio de Janeiro</SelectItem>
<SelectItem value="RN">Rio Grande do Norte</SelectItem>
<SelectItem value="RS">Rio Grande do Sul</SelectItem>
<SelectItem value="RO">Rondônia</SelectItem>
<SelectItem value="RR">Roraima</SelectItem>
<SelectItem value="SC">Santa Catarina</SelectItem>
<SelectItem value="SP">São Paulo</SelectItem>
<SelectItem value="SE">Sergipe</SelectItem>
<SelectItem value="TO">Tocantins</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{/* Medical Information Section */}
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações Médicas</h2>
<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)}
>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="A+">A+</SelectItem>
<SelectItem value="A-">A-</SelectItem>
<SelectItem value="B+">B+</SelectItem>
<SelectItem value="B-">B-</SelectItem>
<SelectItem value="AB+">AB+</SelectItem>
<SelectItem value="AB-">AB-</SelectItem>
<SelectItem value="O+">O+</SelectItem>
<SelectItem value="O-">O-</SelectItem>
</SelectContent>
</Select>
</div>
<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"
/>
</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"
/>
</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"
/>
</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"
/>
</div>
</div>
{/* Insurance Information Section */}
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações de convênio</h2>
<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)}>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Particular">Particular</SelectItem>
<SelectItem value="SUS">SUS</SelectItem>
<SelectItem value="Unimed">Unimed</SelectItem>
<SelectItem value="Bradesco">Bradesco Saúde</SelectItem>
<SelectItem value="Amil">Amil</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="plano">Plano</Label>
<Input id="plano" value={formData.plano} onChange={(e) => handleInputChange("plano", e.target.value)} />
</div>
<div className="space-y-2">
<Label htmlFor="numeroMatricula"> de matrícula</Label>
<Input
id="numeroMatricula"
value={formData.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}
/>
</div>
</div>
<div className="mt-4">
<div className="flex items-center space-x-2">
<Checkbox
id="validadeIndeterminada"
checked={validadeIndeterminada}
onCheckedChange={(checked) => setValidadeIndeterminada(checked === true)}
/>
<Label htmlFor="validadeIndeterminada">Validade Indeterminada</Label>
</div>
</div>
</div>
<div className="flex justify-end gap-4">
<Link href="/manager/home">
<Button type="button" variant="outline">
Cancelar
</Button>
</Link>
<Button type="submit" className="bg-green-600 hover:bg-green-700">
<Save className="w-4 h-4 mr-2" />
Salvar Alterações
</Button>
</div>
</form>
</div>
</ManagerLayout>
)
}

View File

@ -0,0 +1,479 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Upload, Plus, X, ChevronDown } from "lucide-react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import ManagerLayout from "@/components/manager-layout"
export default function NovoMedicoPage() {
const [anexosOpen, setAnexosOpen] = useState(false)
const [anexos, setAnexos] = useState<string[]>([])
const adicionarAnexo = () => {
setAnexos([...anexos, `Documento ${anexos.length + 1}`])
}
const removerAnexo = (index: number) => {
setAnexos(anexos.filter((_, i) => i !== index))
}
return (
<ManagerLayout>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">Novo Médico</h1>
<p className="text-gray-600">Cadastre um novo médico no sistema</p>
</div>
</div>
<form className="space-y-6">
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados Pessoais</h2>
<div className="space-y-6">
{/* Foto */}
<div className="flex items-center gap-4">
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center">
<Upload className="w-8 h-8 text-gray-400" />
</div>
<Button variant="outline" type="button" size="sm">
<Upload className="w-4 h-4 mr-2" />
Carregar Foto
</Button>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="nome" className="text-sm font-medium text-gray-700">
Nome *
</Label>
<Input id="nome" placeholder="Nome completo" required className="mt-1" />
</div>
<div>
<Label htmlFor="nomeSocial" className="text-sm font-medium text-gray-700">
Nome Social
</Label>
<Input id="nomeSocial" placeholder="Nome social ou apelido" className="mt-1" />
</div>
</div>
<div className="grid md:grid-cols-3 gap-4">
<div>
<Label htmlFor="cpf" className="text-sm font-medium text-gray-700">
CPF *
</Label>
<Input id="cpf" placeholder="000.000.000-00" required className="mt-1" />
</div>
<div>
<Label htmlFor="rg" className="text-sm font-medium text-gray-700">
RG
</Label>
<Input id="rg" placeholder="00.000.000-0" className="mt-1" />
</div>
<div>
<Label htmlFor="crm" className="text-sm font-medium text-gray-700">
CRM *
</Label>
<Input id="crm" placeholder="CRM/UF 12345" required className="mt-1" />
</div>
</div>
<div>
<Label htmlFor="outrosDocumentos" className="text-sm font-medium text-gray-700">
Outros Documentos
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="cnh">CNH</SelectItem>
<SelectItem value="passaporte">Passaporte</SelectItem>
<SelectItem value="carteira-trabalho">Carteira de Trabalho</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid md:grid-cols-3 gap-4">
<div>
<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" />
<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" />
<span className="text-sm">Feminino</span>
</label>
</div>
</div>
<div>
<Label htmlFor="dataNascimento" className="text-sm font-medium text-gray-700">
Data de Nascimento
</Label>
<Input id="dataNascimento" type="date" className="mt-1" />
</div>
<div>
<Label htmlFor="estadoCivil" className="text-sm font-medium text-gray-700">
Estado Civil
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="solteiro">Solteiro(a)</SelectItem>
<SelectItem value="casado">Casado(a)</SelectItem>
<SelectItem value="divorciado">Divorciado(a)</SelectItem>
<SelectItem value="viuvo">Viúvo(a)</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="etnia" className="text-sm font-medium text-gray-700">
Etnia
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="branca">Branca</SelectItem>
<SelectItem value="preta">Preta</SelectItem>
<SelectItem value="parda">Parda</SelectItem>
<SelectItem value="amarela">Amarela</SelectItem>
<SelectItem value="indigena">Indígena</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="raca" className="text-sm font-medium text-gray-700">
Raça
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="branca">Branca</SelectItem>
<SelectItem value="preta">Preta</SelectItem>
<SelectItem value="parda">Parda</SelectItem>
<SelectItem value="amarela">Amarela</SelectItem>
<SelectItem value="indigena">Indígena</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="naturalidade" className="text-sm font-medium text-gray-700">
Naturalidade
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="aracaju">Aracaju</SelectItem>
<SelectItem value="salvador">Salvador</SelectItem>
<SelectItem value="recife">Recife</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="nacionalidade" className="text-sm font-medium text-gray-700">
Nacionalidade
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="brasileira">Brasileira</SelectItem>
<SelectItem value="estrangeira">Estrangeira</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="profissao" className="text-sm font-medium text-gray-700">
Profissão
</Label>
<Input id="profissao" placeholder="Médico" className="mt-1" />
</div>
<div>
<Label htmlFor="especialidade" className="text-sm font-medium text-gray-700">
Especialidade *
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="cardiologia">Cardiologia</SelectItem>
<SelectItem value="pediatria">Pediatria</SelectItem>
<SelectItem value="ortopedia">Ortopedia</SelectItem>
<SelectItem value="ginecologia">Ginecologia</SelectItem>
<SelectItem value="neurologia">Neurologia</SelectItem>
<SelectItem value="dermatologia">Dermatologia</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="nomeMae" className="text-sm font-medium text-gray-700">
Nome da Mãe
</Label>
<Input id="nomeMae" placeholder="Nome da mãe" className="mt-1" />
</div>
<div>
<Label htmlFor="nomePai" className="text-sm font-medium text-gray-700">
Nome do Pai
</Label>
<Input id="nomePai" placeholder="Nome do pai" className="mt-1" />
</div>
</div>
<div>
<Label htmlFor="nomeEsposo" className="text-sm font-medium text-gray-700">
Nome do Esposo(a)
</Label>
<Input id="nomeEsposo" placeholder="Nome do esposo(a)" className="mt-1" />
</div>
<div>
<Label htmlFor="codigoLegado" className="text-sm font-medium text-gray-700">
Código Legado
</Label>
<Input id="codigoLegado" placeholder="Código do sistema anterior" className="mt-1" />
</div>
<div>
<Label htmlFor="observacoes" className="text-sm font-medium text-gray-700">
Observações
</Label>
<Textarea
id="observacoes"
placeholder="Observações gerais sobre o médico"
className="min-h-[100px] mt-1"
/>
</div>
<Collapsible open={anexosOpen} onOpenChange={setAnexosOpen}>
<CollapsibleTrigger asChild>
<Button variant="ghost" type="button" className="w-full justify-between p-0 h-auto text-left">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-gray-400 rounded-sm flex items-center justify-center">
<span className="text-white text-xs">📎</span>
</div>
<span className="text-sm font-medium text-gray-700">Anexos do médico</span>
</div>
<ChevronDown className={`w-4 h-4 transition-transform ${anexosOpen ? "rotate-180" : ""}`} />
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-4 mt-4">
{anexos.map((anexo, index) => (
<div key={index} className="flex items-center justify-between p-3 border rounded-lg bg-gray-50">
<span className="text-sm">{anexo}</span>
<Button variant="ghost" size="sm" onClick={() => removerAnexo(index)} type="button">
<X className="w-4 h-4" />
</Button>
</div>
))}
<Button variant="outline" onClick={adicionarAnexo} type="button" size="sm">
<Plus className="w-4 h-4 mr-2" />
Adicionar Anexo
</Button>
</CollapsibleContent>
</Collapsible>
</div>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Contato</h2>
<div className="space-y-4">
<div className="grid md:grid-cols-3 gap-4">
<div>
<Label htmlFor="email" className="text-sm font-medium text-gray-700">
E-mail
</Label>
<Input id="email" type="email" placeholder="email@exemplo.com" className="mt-1" />
</div>
<div>
<Label htmlFor="celular" className="text-sm font-medium text-gray-700">
Celular
</Label>
<div className="flex mt-1">
<Select>
<SelectTrigger className="w-20 rounded-r-none">
<SelectValue placeholder="+55" />
</SelectTrigger>
<SelectContent>
<SelectItem value="+55">+55</SelectItem>
</SelectContent>
</Select>
<Input placeholder="(XX) XXXXX-XXXX" className="rounded-l-none" />
</div>
</div>
<div>
<Label htmlFor="telefone1" className="text-sm font-medium text-gray-700">
Telefone 1
</Label>
<Input id="telefone1" placeholder="(XX) XXXX-XXXX" className="mt-1" />
</div>
</div>
<div>
<Label htmlFor="telefone2" className="text-sm font-medium text-gray-700">
Telefone 2
</Label>
<Input id="telefone2" placeholder="(XX) XXXX-XXXX" className="mt-1" />
</div>
</div>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Endereço</h2>
<div className="space-y-4">
<div>
<Label htmlFor="cep" className="text-sm font-medium text-gray-700">
CEP
</Label>
<Input id="cep" placeholder="00000-000" className="mt-1 max-w-xs" />
</div>
<div className="grid md:grid-cols-3 gap-4">
<div className="md:col-span-2">
<Label htmlFor="endereco" className="text-sm font-medium text-gray-700">
Endereço
</Label>
<Input id="endereco" placeholder="Rua, Avenida..." className="mt-1" />
</div>
<div>
<Label htmlFor="numero" className="text-sm font-medium text-gray-700">
Número
</Label>
<Input id="numero" placeholder="123" className="mt-1" />
</div>
</div>
<div>
<Label htmlFor="complemento" className="text-sm font-medium text-gray-700">
Complemento
</Label>
<Input id="complemento" placeholder="Apto, Bloco..." className="mt-1" />
</div>
<div className="grid md:grid-cols-3 gap-4">
<div>
<Label htmlFor="bairro" className="text-sm font-medium text-gray-700">
Bairro
</Label>
<Input id="bairro" placeholder="Bairro" className="mt-1" />
</div>
<div>
<Label htmlFor="cidade" className="text-sm font-medium text-gray-700">
Cidade
</Label>
<Input id="cidade" placeholder="Cidade" className="mt-1" />
</div>
<div>
<Label htmlFor="estado" className="text-sm font-medium text-gray-700">
Estado
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="SE">Sergipe</SelectItem>
<SelectItem value="BA">Bahia</SelectItem>
<SelectItem value="AL">Alagoas</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações Profissionais</h2>
<div className="space-y-4">
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="numeroConselho" className="text-sm font-medium text-gray-700">
Número do Conselho
</Label>
<Input id="numeroConselho" placeholder="Número do CRM" className="mt-1" />
</div>
<div>
<Label htmlFor="ufConselho" className="text-sm font-medium text-gray-700">
UF do Conselho
</Label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="SE">SE</SelectItem>
<SelectItem value="BA">BA</SelectItem>
<SelectItem value="AL">AL</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="dataFormatura" className="text-sm font-medium text-gray-700">
Data de Formatura
</Label>
<Input id="dataFormatura" type="date" className="mt-1" />
</div>
<div>
<Label htmlFor="instituicaoFormacao" className="text-sm font-medium text-gray-700">
Instituição de Formação
</Label>
<Input id="instituicaoFormacao" placeholder="Nome da universidade" className="mt-1" />
</div>
</div>
<div>
<Label htmlFor="especialidades" className="text-sm font-medium text-gray-700">
Especialidades Adicionais
</Label>
<Textarea
id="especialidades"
placeholder="Liste outras especialidades ou subespecialidades..."
className="min-h-[80px] mt-1"
/>
</div>
</div>
</div>
<div className="flex justify-end gap-4">
<Link href="/manager/home">
<Button variant="outline">Cancelar</Button>
</Link>
<Button type="submit" className="bg-green-600 hover:bg-green-700">
Salvar Médico
</Button>
</div>
</form>
</div>
</ManagerLayout>
)
}

309
app/manager/home/page.tsx Normal file
View File

@ -0,0 +1,309 @@
"use client";
import React, { useEffect, useState } from "react"
import ManagerLayout from "@/components/manager-layout";
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Plus, Edit, Trash2, Eye, Calendar, Filter } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
export default function DoctorsPage() {
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false)
const [doctorDetails, setDoctorDetails] = useState<any | null>(null)
const openDetailsDialog = async (doctorId: string) => {
setDetailsDialogOpen(true)
setDoctorDetails(null)
try {
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${doctorId}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const json = await res.json()
setDoctorDetails(json?.data ?? null)
} catch (e: any) {
setDoctorDetails({ error: e?.message || "Erro ao buscar detalhes" })
}
}
const [searchTerm, setSearchTerm] = useState("")
const [especialidadeFilter, setEspecialidadeFilter] = useState("all")
const [statusFilter, setStatusFilter] = useState("all")
const [doctors, setDoctors] = useState<any[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
const [doctorToDelete, setDoctorToDelete] = useState<string | null>(null)
const [page, setPage] = useState(1)
const [hasNext, setHasNext] = useState(true)
const [isFetching, setIsFetching] = useState(false)
const observerRef = React.useRef<HTMLDivElement | null>(null)
const fetchMedicos = React.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 ?? "",
crm: p.crm ?? "", // mock não tem crm, pode deixar vazio
especialidade: p.especialidade ?? "", // mock não tem especialidade, pode deixar vazio
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 ?? "",
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? "",
status: p.status ?? "",
}))
setDoctors((prev) => [...prev, ...mapped])
setHasNext(Boolean(json?.pagination?.has_next))
setPage(pageToFetch + 1)
} catch (e: any) {
setError(e?.message || "Erro ao buscar médicos")
} finally {
setIsFetching(false)
}
}, [isFetching, hasNext])
React.useEffect(() => {
fetchMedicos(page)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (!observerRef.current || !hasNext) return
const observer = new window.IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !isFetching && hasNext) {
fetchMedicos(page)
}
})
observer.observe(observerRef.current)
return () => {
if (observerRef.current) observer.unobserve(observerRef.current)
}
}, [fetchMedicos, page, hasNext, isFetching])
const handleDeleteDoctor = async (doctorId: string) => {
try {
await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${doctorId}`, {
method: "DELETE",
})
} catch { }
setDoctors((prev) => prev.filter((doctor) => String(doctor.id) !== String(doctorId)))
setDeleteDialogOpen(false)
setDoctorToDelete(null)
}
const openDeleteDialog = (doctorId: string) => {
setDoctorToDelete(doctorId)
setDeleteDialogOpen(true)
}
const filteredDoctors = doctors.filter((doctor) => {
const matchesSearch =
doctor.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
doctor.crm.toLowerCase().includes(searchTerm.toLowerCase()) ||
doctor.telefone.includes(searchTerm)
const matchesEspecialidade = especialidadeFilter === "all" || doctor.especialidade === especialidadeFilter
const matchesStatus = statusFilter === "all" || doctor.status === statusFilter
return matchesSearch && matchesEspecialidade && matchesStatus
})
return (
<ManagerLayout>
<div className="space-y-6">
{/* ...layout e filtros... */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">Médicos</h1>
<p className="text-gray-600">Gerencie as informações dos médicos</p>
</div>
<Link href="/manager/home/novo">
<Button className="bg-green-600 hover:bg-green-700">
<Plus className="w-4 h-4 mr-2" />
Adicionar
</Button>
</Link>
</div>
<div className="flex items-center gap-4 bg-white p-4 rounded-lg border border-gray-200">
{/* ...filtros... */}
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700">Especialidade</span>
<Select value={especialidadeFilter} onValueChange={setEspecialidadeFilter}>
<SelectTrigger className="w-40">
<SelectValue placeholder="Selecione a Especialidade" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todas</SelectItem>
<SelectItem value="Cardiologia">Cardiologia</SelectItem>
<SelectItem value="Pediatria">Pediatria</SelectItem>
<SelectItem value="Ortopedia">Ortopedia</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700">Status</span>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-32">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todos</SelectItem>
<SelectItem value="Ativo">Ativo</SelectItem>
<SelectItem value="Inativo">Inativo</SelectItem>
</SelectContent>
</Select>
</div>
<Button variant="outline" className="ml-auto bg-transparent">
<Filter className="w-4 h-4 mr-2" />
Filtro avançado
</Button>
</div>
<div className="bg-white rounded-lg border border-gray-200">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="text-left p-4 font-medium text-gray-700">Nome</th>
<th className="text-left p-4 font-medium text-gray-700">CRM</th>
<th className="text-left p-4 font-medium text-gray-700">Telefone</th>
<th className="text-left p-4 font-medium text-gray-700">Cidade</th>
<th className="text-left p-4 font-medium text-gray-700">Estado</th>
<th className="text-left p-4 font-medium text-gray-700">Último atendimento</th>
<th className="text-left p-4 font-medium text-gray-700">Próximo atendimento</th>
<th className="text-left p-4 font-medium text-gray-700">Ações</th>
</tr>
</thead>
<tbody>
{error ? (
<tr>
<td colSpan={8} className="p-6 text-red-600">{`Erro: ${error}`}</td>
</tr>
) : filteredDoctors.length === 0 ? (
<tr>
<td colSpan={8} className="p-8 text-center text-gray-500">Nenhum registro encontrado</td>
</tr>
) : (
filteredDoctors.map((doctor) => (
<tr key={doctor.id} className="border-b border-gray-100 hover:bg-gray-50">
<td className="p-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-600 font-medium text-sm">{doctor.nome?.charAt(0) || "?"}</span>
</div>
<span className="font-medium text-gray-900">{doctor.nome}</span>
</div>
</td>
<td className="p-4 text-gray-600">{doctor.crm}</td>
<td className="p-4 text-gray-600">{doctor.telefone}</td>
<td className="p-4 text-gray-600">{doctor.cidade}</td>
<td className="p-4 text-gray-600">{doctor.estado}</td>
<td className="p-4 text-gray-600">{doctor.ultimoAtendimento}</td>
<td className="p-4 text-gray-600">{doctor.proximoAtendimento}</td>
<td className="p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="text-blue-600 cursor-pointer">
Ações
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(doctor.id))}>
<Eye className="w-4 h-4 mr-2" />
Ver detalhes
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/manager/home/${doctor.id}/editar`}>
<Edit className="w-4 h-4 mr-2" />
Editar
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Calendar className="w-4 h-4 mr-2" />
Ver agenda
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(doctor.id))}>
<Trash2 className="w-4 h-4 mr-2" />
Excluir
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
</tr>
))
)}
</tbody>
</table>
<div ref={observerRef} style={{ height: 1 }} />
{isFetching && (
<div className="p-4 text-center text-gray-500">Carregando mais médicos...</div>
)}
</div>
</div>
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
<AlertDialogDescription>
Tem certeza que deseja excluir este médico? Esta ação não pode ser desfeita.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={() => doctorToDelete && handleDeleteDoctor(doctorToDelete)}
className="bg-red-600 hover:bg-red-700"
>
Excluir
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Modal de detalhes do médico */}
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Detalhes do Médico</AlertDialogTitle>
<AlertDialogDescription>
{doctorDetails === null ? (
<div className="text-gray-500">Carregando...</div>
) : doctorDetails?.error ? (
<div className="text-red-600">{doctorDetails.error}</div>
) : (
<div className="space-y-2 text-left">
<div><strong>Nome:</strong> {doctorDetails.nome}</div>
<div><strong>Telefone:</strong> {doctorDetails?.contato?.celular ?? doctorDetails?.contato?.telefone1 ?? doctorDetails?.telefone ?? ""}</div>
<div><strong>Cidade:</strong> {doctorDetails?.endereco?.cidade ?? doctorDetails?.cidade ?? ""}</div>
<div><strong>Estado:</strong> {doctorDetails?.endereco?.estado ?? doctorDetails?.estado ?? ""}</div>
<div><strong>Convênio:</strong> {doctorDetails.convenio ?? ""}</div>
<div><strong>VIP:</strong> {doctorDetails.vip ? "Sim" : "Não"}</div>
<div><strong>Status:</strong> {doctorDetails.status ?? ""}</div>
<div><strong>Último atendimento:</strong> {doctorDetails.ultimo_atendimento ?? doctorDetails.ultimoAtendimento ?? ""}</div>
<div><strong>Próximo atendimento:</strong> {doctorDetails.proximo_atendimento ?? doctorDetails.proximoAtendimento ?? ""}</div>
</div>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Fechar</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</ManagerLayout>
);
}

155
app/manager/login/page.tsx Normal file
View File

@ -0,0 +1,155 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import { useToast } from "@/hooks/use-toast"
import { Eye, EyeOff, Mail, Lock, Stethoscope, Loader2, IdCard } from "lucide-react"
import Link from "next/link"
interface LoginForm {
email: string
password: string
}
export default function ManagerLogin() {
const [form, setForm] = useState<LoginForm>({ email: "", password: "" })
const [showPassword, setShowPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const { toast } = useToast()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
// Simular autenticação
setTimeout(() => {
if (form.email && form.password) {
const managerData = {
id: "1",
name: "Arthur Cavalcante",
email: form.email,
phone: "(11) 98888-8888",
cpf: "987.654.321-00",
department: "Gerente",
permissions: ["manage_user", "manage_doctors", "create_reports"],
}
localStorage.setItem("managerData", JSON.stringify(managerData))
localStorage.setItem("userType", "manager")
toast({
title: "Login realizado com sucesso!",
description: "Bem-vindo ao sistema, " + managerData.name,
})
router.push("/manager/home")
} else {
toast({
title: "Erro no login",
description: "Por favor, preencha todos os campos.",
variant: "destructive",
})
}
setIsLoading(false)
}, 1500)
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md shadow-xl border-0 bg-white/80 backdrop-blur-sm">
<CardHeader className="text-center space-y-4 pb-8">
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<IdCard className="w-8 h-8 text-blue-600" />
</div>
<div>
<CardTitle className="text-2xl font-bold text-gray-900">Área do Gestor</CardTitle>
<CardDescription className="text-gray-600 mt-2">Acesse o sistema médico</CardDescription>
</div>
</CardHeader>
<CardContent className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="email" className="text-sm font-medium text-gray-700">
E-mail
</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="email"
type="email"
placeholder="gestor@clinica.com"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
className="pl-10 h-11 border-gray-200 focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-sm font-medium text-gray-700">
Senha
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Digite sua senha"
value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })}
className="pl-10 pr-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<Button
type="submit"
className="w-full h-11 bg-blue-600 hover:bg-blue-700 text-white font-medium"
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Entrando...
</>
) : (
"Entrar"
)}
</Button>
</form>
<div className="relative">
<Separator className="my-6" />
<span className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white px-3 text-sm text-gray-500">
ou
</span>
</div>
<div className="text-center">
<Link href="/" className="text-sm text-blue-600 hover:text-blue-700 font-medium hover:underline">
Voltar à página inicial
</Link>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@ -15,6 +15,7 @@ import { Upload, Plus, X, ChevronDown } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import SecretaryLayout from "@/components/secretary-layout"; import SecretaryLayout from "@/components/secretary-layout";
import { patientsService } from "@/services/patientsApi.mjs";
export default function NovoPacientePage() { export default function NovoPacientePage() {
const [anexosOpen, setAnexosOpen] = useState(false); const [anexosOpen, setAnexosOpen] = useState(false);
@ -35,79 +36,64 @@ export default function NovoPacientePage() {
e.preventDefault(); e.preventDefault();
if (isLoading) return; if (isLoading) return;
setIsLoading(true); setIsLoading(true);
const form = e.currentTarget; const form = e.currentTarget;
const formData = new FormData(form); const formData = new FormData(form);
const apiPayload = { const apiPayload = {
nome: formData.get("nome") as string, full_name: (formData.get("nome") as string) || "", // obrigatório
nome_social: (formData.get("nomeSocial") as string) || null, social_name: (formData.get("nomeSocial") as string) || undefined,
cpf: formData.get("cpf") as string, cpf: (formData.get("cpf") as string) || "", // obrigatório
rg: (formData.get("rg") as string) || null, email: (formData.get("email") as string) || "", // obrigatório
outros_documentos: phone_mobile: (formData.get("celular") as string) || "", // obrigatório
(formData.get("outrosDocumentosTipo") as string) || (formData.get("outrosDocumentosNumero") as string) birth_date: formData.get("dataNascimento") ? new Date(formData.get("dataNascimento") as string) : undefined,
? { sex: (formData.get("sexo") as string) || undefined,
tipo: (formData.get("outrosDocumentosTipo") as string) || undefined, blood_type: (formData.get("tipoSanguineo") as string) || undefined,
numero: (formData.get("outrosDocumentosNumero") 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,
: null, cep: (formData.get("cep") as string) || undefined,
sexo: (formData.get("sexo") as string) || null, street: (formData.get("endereco") as string) || undefined,
data_nascimento: (formData.get("dataNascimento") as string) || null, number: (formData.get("numero") as string) || undefined,
etnia: (formData.get("etnia") as string) || null, complement: (formData.get("complemento") as string) || undefined,
raca: (formData.get("raca") as string) || null, neighborhood: (formData.get("bairro") as string) || undefined,
naturalidade: (formData.get("naturalidade") as string) || null, city: (formData.get("cidade") as string) || undefined,
nacionalidade: (formData.get("nacionalidade") as string) || null, state: (formData.get("estado") as string) || undefined,
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,
}; };
const errors: string[] = []; const errors: string[] = [];
const nome = apiPayload.nome?.trim() || ""; const fullName = apiPayload.full_name?.trim() || "";
if (!nome || nome.length < 2 || nome.length > 255) errors.push("Nome deve ter entre 2 e 255 caracteres."); if (!fullName || fullName.length < 2 || fullName.length > 255) {
const cpf = apiPayload.cpf || ""; errors.push("Nome deve ter entre 2 e 255 caracteres.");
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.");
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) { if (errors.length) {
toast({ title: "Corrija os campos", description: errors[0] }); toast({ title: "Corrija os campos", description: errors[0] });
setIsLoading(false); setIsLoading(false);
@ -115,27 +101,15 @@ export default function NovoPacientePage() {
} }
try { try {
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", { const res = await patientsService.create(apiPayload);
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);
}
let message = "Paciente cadastrado com sucesso"; let message = "Paciente cadastrado com sucesso";
try { try {
const payload = await res.json(); if (!res[0].id) {
if (payload?.success === false) { throw new Error(`${res.error} ${res.message}`|| "A API retornou erro");
throw new Error(payload?.message || "A API retornou erro"); } else {
console.log(message)
} }
if (payload?.message) message = String(payload.message);
} catch {} } catch {}
toast({ toast({

View File

@ -8,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Plus, Edit, Trash2, Eye, Calendar, Filter } from "lucide-react"; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import SecretaryLayout from "@/components/secretary-layout"; import SecretaryLayout from "@/components/secretary-layout";
import { patientsService } from "@/services/patientsApi.mjs"
export default function PacientesPage() { export default function PacientesPage() {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
@ -28,10 +29,8 @@ export default function PacientesPage() {
setDetailsDialogOpen(true); setDetailsDialogOpen(true);
setPatientDetails(null); setPatientDetails(null);
try { try {
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientId}`); const res = await patientsService.getById(patientId);
if (!res.ok) throw new Error(`HTTP ${res.status}`); setPatientDetails(res[0]);
const json = await res.json();
setPatientDetails(json?.data ?? null);
} catch (e: any) { } catch (e: any) {
setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" });
} }
@ -43,25 +42,28 @@ export default function PacientesPage() {
setIsFetching(true); setIsFetching(true);
setError(null); setError(null);
try { try {
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes?page=${pageToFetch}&limit=20`); const res = await patientsService.list();
if (!res.ok) throw new Error(`HTTP ${res.status}`); const mapped = res.map((p: any) => ({
const json = await res.json(); id: String(p.id ?? ""),
const items = Array.isArray(json?.data) ? json.data : []; nome: p.full_name ?? "",
const mapped = items.map((p: any) => ({ telefone: p.phone_mobile ?? p.phone1 ?? "",
id: String(p.id ?? ""), cidade: p.city ?? "",
nome: p.nome ?? "", estado: p.state ?? "",
telefone: p?.contato?.celular ?? p?.contato?.telefone1 ?? p?.telefone ?? "", ultimoAtendimento: p.last_visit_at ?? "",
cidade: p?.endereco?.cidade ?? p?.cidade ?? "", proximoAtendimento: p.next_appointment_at ?? "",
estado: p?.endereco?.estado ?? p?.estado ?? "", vip: Boolean(p.vip ?? false),
ultimoAtendimento: p.ultimo_atendimento ?? p.ultimoAtendimento ?? undefined, convenio: p.convenio ?? "", // se não existir, fica vazio
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? undefined, status: p.status ?? undefined,
convenio: p.convenio ?? "", }));
vip: Boolean(p.vip ?? false),
status: p.status ?? undefined, setPatients((prev) => {
})); const all = [...prev, ...mapped];
setPatients((prev) => [...prev, ...mapped]); const unique = Array.from(new Map(all.map(p => [p.id, p])).values());
setHasNext(Boolean(json?.pagination?.has_next)); return unique;
setPage(pageToFetch + 1); });
if (mapped.length === 0) setHasNext(false); // parar carregamento
else setPage(prev => prev + 1);
} catch (e: any) { } catch (e: any) {
setError(e?.message || "Erro ao buscar pacientes"); setError(e?.message || "Erro ao buscar pacientes");
} finally { } finally {
@ -89,9 +91,21 @@ export default function PacientesPage() {
}; };
}, [fetchPacientes, page, hasNext, isFetching]); }, [fetchPacientes, page, hasNext, isFetching]);
const handleDeletePatient = (patientId: string) => { const handleDeletePatient = async (patientId: string) => {
// Remove from current list (client-side deletion) // 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); setDeleteDialogOpen(false);
setPatientToDelete(null); setPatientToDelete(null);
}; };
@ -112,35 +126,63 @@ export default function PacientesPage() {
return ( return (
<SecretaryLayout> <SecretaryLayout>
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between"> <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Pacientes</h1> <h1 className="text-xl md:text-2xl font-bold text-gray-900">Pacientes</h1>
<p className="text-gray-600">Gerencie as informações de seus pacientes</p> <p className="text-gray-600 text-sm md:text-base">Gerencie as informações de seus pacientes</p>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Link href="/secretary/pacientes/novo"> <Link href="/secretary/pacientes/novo">
<Button className="bg-blue-600 hover:bg-blue-700"> <Button className="bg-blue-600 hover:bg-blue-700 w-full md:w-auto">
<Plus className="w-4 h-4 mr-2" /> <Plus className="w-4 h-4 mr-2" />
Adicionar Adicionar
</Button> </Button>
</Link> </Link>
</div> </div>
</div> </div>
<div className="flex items-center gap-4 bg-white p-4 rounded-lg border border-gray-200"> <div className="flex flex-col md:flex-row flex-wrap gap-4 bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center gap-2"> {/* Convênio */}
<span className="text-sm font-medium text-gray-700">Convênio</span> <div className="flex items-center gap-2 w-full md:w-auto">
<Select value={convenioFilter} onValueChange={setConvenioFilter}> <span className="text-sm font-medium text-gray-700">Convênio</span>
<SelectTrigger className="w-40"> <Select value={convenioFilter} onValueChange={setConvenioFilter}>
<SelectValue placeholder="Selecione o Convênio" /> <SelectTrigger className="w-full md:w-40">
</SelectTrigger> <SelectValue placeholder="Selecione o Convênio" />
<SelectContent> </SelectTrigger>
<SelectItem value="all">Todos</SelectItem> <SelectContent>
<SelectItem value="Particular">Particular</SelectItem> <SelectItem value="all">Todos</SelectItem>
<SelectItem value="SUS">SUS</SelectItem> <SelectItem value="Particular">Particular</SelectItem>
<SelectItem value="Unimed">Unimed</SelectItem> <SelectItem value="SUS">SUS</SelectItem>
</SelectContent> <SelectItem value="Unimed">Unimed</SelectItem>
</Select> </SelectContent>
</Select>
</div>
<div className="flex items-center gap-2 w-full md:w-auto">
<span className="text-sm font-medium text-gray-700">VIP</span>
<Select value={vipFilter} onValueChange={setVipFilter}>
<SelectTrigger className="w-full md:w-32">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todos</SelectItem>
<SelectItem value="vip">VIP</SelectItem>
<SelectItem value="regular">Regular</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2 w-full md:w-auto">
<span className="text-sm font-medium text-gray-700">Aniversariantes</span>
<Select>
<SelectTrigger className="w-full md:w-32">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="today">Hoje</SelectItem>
<SelectItem value="week">Esta semana</SelectItem>
<SelectItem value="month">Este mês</SelectItem>
</SelectContent>
</Select>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -171,27 +213,28 @@ export default function PacientesPage() {
</Select> </Select>
</div> </div>
<Button variant="outline" className="ml-auto bg-transparent"> <Button variant="outline" className="ml-auto bg-transparent w-full md:w-auto">
<Filter className="w-4 h-4 mr-2" /> <Filter className="w-4 h-4 mr-2" />
Filtro avançado Filtro avançado
</Button> </Button>
</div> </div>
<div className="bg-white rounded-lg border border-gray-200"> <div className="bg-white rounded-lg border border-gray-200">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
{error ? ( {error ? (
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div> <div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
) : ( ) : (
<table className="w-full"> <table className="w-full min-w-[600px]">
<thead className="bg-gray-50 border-b border-gray-200"> <thead className="bg-gray-50 border-b border-gray-200">
<tr> <tr>
<th className="text-left p-4 font-medium text-gray-700">Nome</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Nome</th>
<th className="text-left p-4 font-medium text-gray-700">Telefone</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Telefone</th>
<th className="text-left p-4 font-medium text-gray-700">Cidade</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Cidade</th>
<th className="text-left p-4 font-medium text-gray-700">Estado</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Estado</th>
<th className="text-left p-4 font-medium text-gray-700">Último atendimento</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Último atendimento</th>
<th className="text-left p-4 font-medium text-gray-700">Próximo atendimento</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Próximo atendimento</th>
<th className="text-left p-4 font-medium text-gray-700">Ações</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Ações</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -282,33 +325,23 @@ export default function PacientesPage() {
<div className="text-red-600">{patientDetails.error}</div> <div className="text-red-600">{patientDetails.error}</div>
) : ( ) : (
<div className="space-y-2 text-left"> <div className="space-y-2 text-left">
<div> <p><strong>Nome:</strong> {patientDetails.full_name}</p>
<strong>Nome:</strong> {patientDetails.nome} <p><strong>CPF:</strong> {patientDetails.cpf}</p>
</div> <p><strong>Email:</strong> {patientDetails.email}</p>
<div> <p><strong>Telefone:</strong> {patientDetails.phone_mobile ?? patientDetails.phone1 ?? patientDetails.phone2 ?? "-"}</p>
<strong>Telefone:</strong> {patientDetails?.contato?.celular ?? patientDetails?.contato?.telefone1 ?? patientDetails?.telefone ?? ""} <p><strong>Nome social:</strong> {patientDetails.social_name ?? "-"}</p>
</div> <p><strong>Sexo:</strong> {patientDetails.sex ?? "-"}</p>
<div> <p><strong>Tipo sanguíneo:</strong> {patientDetails.blood_type ?? "-"}</p>
<strong>Cidade:</strong> {patientDetails?.endereco?.cidade ?? patientDetails?.cidade ?? ""} <p><strong>Peso:</strong> {patientDetails.weight_kg ?? "-"}{patientDetails.weight_kg ? "kg": ""}</p>
</div> <p><strong>Altura:</strong> {patientDetails.height_m ?? "-"}{patientDetails.height_m ? "m": ""}</p>
<div> <p><strong>IMC:</strong> {patientDetails.bmi ?? "-"}</p>
<strong>Estado:</strong> {patientDetails?.endereco?.estado ?? patientDetails?.estado ?? ""} <p><strong>Endereço:</strong> {patientDetails.street ?? "-"}</p>
</div> <p><strong>Bairro:</strong> {patientDetails.neighborhood ?? "-"}</p>
<div> <p><strong>Cidade:</strong> {patientDetails.city ?? "-"}</p>
<strong>Convênio:</strong> {patientDetails.convenio ?? ""} <p><strong>Estado:</strong> {patientDetails.state ?? "-"}</p>
</div> <p><strong>CEP:</strong> {patientDetails.cep ?? "-"}</p>
<div> <p><strong>Criado em:</strong> {patientDetails.created_at ?? "-"}</p>
<strong>VIP:</strong> {patientDetails.vip ? "Sim" : "Não"} <p><strong>Atualizado em:</strong> {patientDetails.updated_at ?? "-"}</p>
</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>
</div> </div>
)} )}
</AlertDialogDescription> </AlertDialogDescription>

View File

@ -0,0 +1,195 @@
"use client";
import type React from "react";
import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Search, Bell, Calendar, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react";
interface FinancierData {
id: string,
name: string,
email: string,
phone: string,
cpf: string,
department: string,
permissions: object,
}
interface PatientLayoutProps {
children: React.ReactNode;
}
export default function FinancierLayout({ children }: PatientLayoutProps) {
const [financierData, setFinancierData] = useState<FinancierData | null>(null);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
const data = localStorage.getItem("financierData");
if (data) {
setFinancierData(JSON.parse(data));
} else {
router.push("/finance/login");
}
}, [router]);
const handleLogout = () => {
setShowLogoutDialog(true);
};
const confirmLogout = () => {
localStorage.removeItem("financierData");
setShowLogoutDialog(false);
router.push("/");
};
const cancelLogout = () => {
setShowLogoutDialog(false);
};
const menuItems = [
{
href: "#",
icon: Home,
label: "Dashboard",
// Botão para o dashboard do médico
},
{
href: "#",
icon: Calendar,
label: "Relatórios financeiros",
// Botão para o dashboard do médico
},
{
href: "#",
icon: User,
label: "Finanças Gerais",
// Botão para página do editor de laudo
},
{
href: "#",
icon: Calendar,
label: "Configurações",
// Botão para página de consultas marcadas do médico atual
},
];
if (!financierData) {
return <div>Carregando...</div>;
}
return (
<div className="min-h-screen bg-gray-50 flex">
{/* Sidebar */}
<div className={`bg-white border-r border-gray-200 transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
<div className="p-4 border-b border-gray-200">
<div className="flex items-center justify-between">
{!sidebarCollapsed && (
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<div className="w-4 h-4 bg-white rounded-sm"></div>
</div>
<span className="font-semibold text-gray-900">Hospital System</span>
</div>
)}
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
</Button>
</div>
</div>
<nav className="flex-1 p-2 overflow-y-auto">
{menuItems.map((item) => {
const Icon = item.icon;
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
return (
<Link key={item.href} href={item.href}>
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-blue-50 text-blue-600 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50"}`}>
<Icon className="w-5 h-5 flex-shrink-0" />
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
</div>
</Link>
);
})}
</nav>
<div className="border-t p-4 mt-auto">
<div className="flex items-center space-x-3 mb-4">
<Avatar>
<AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{financierData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">{financierData.name}</p>
<p className="text-xs text-gray-500 truncate">{financierData.department}</p>
</div>
</div>
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
Sair
</Button>
</div>
</div>
{/* Main Content */}
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
{/* Header */}
<header className="bg-white border-b border-gray-200 px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1 max-w-md">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input placeholder="Buscar paciente" className="pl-10 bg-gray-50 border-gray-200" />
</div>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" className="relative">
<Bell className="w-5 h-5" />
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-red-500 text-white text-xs">1</Badge>
</Button>
</div>
</div>
</header>
{/* Page Content */}
<main className="flex-1 p-6">{children}</main>
</div>
{/* Logout confirmation dialog */}
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Confirmar Saída</DialogTitle>
<DialogDescription>Deseja realmente sair do sistema? Você precisará fazer login novamente para acessar sua conta.</DialogDescription>
</DialogHeader>
<DialogFooter className="flex gap-2">
<Button variant="outline" onClick={cancelLogout}>
Cancelar
</Button>
<Button variant="destructive" onClick={confirmLogout}>
<LogOut className="mr-2 h-4 w-4" />
Sair
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View File

@ -112,7 +112,7 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
return ( return (
<div className="min-h-screen bg-gray-50 flex"> <div className="min-h-screen bg-gray-50 flex">
{/* Sidebar */} {/* Sidebar */}
<div <div
className={`bg-white border-r border-gray-200 transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} h-screen flex flex-col`} className={`bg-white border-r border-gray-200 transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} h-screen flex flex-col`}
> >

View File

@ -0,0 +1,201 @@
"use client";
import type React from "react";
import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Search, Bell, Calendar, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react";
interface ManagerData {
id: string,
name: string,
email: string,
phone: string,
cpf: string,
department: string,
permissions: object,
}
interface PatientLayoutProps {
children: React.ReactNode;
}
export default function ManagerLayout({ children }: PatientLayoutProps) {
const [managerData, setManagerData] = useState<ManagerData | null>(null);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
const data = localStorage.getItem("managerData");
if (data) {
setManagerData(JSON.parse(data));
} else {
router.push("/manager/login");
}
}, [router]);
const handleLogout = () => {
setShowLogoutDialog(true);
};
const confirmLogout = () => {
localStorage.removeItem("managerData");
setShowLogoutDialog(false);
router.push("/");
};
const cancelLogout = () => {
setShowLogoutDialog(false);
};
const menuItems = [
{
href: "#",
icon: Home,
label: "Dashboard",
// Botão para o dashboard do médico
},
{
href: "#",
icon: Calendar,
label: "Relatórios gerenciais",
// Botão para o dashboard do médico
},
{
href: "#",
icon: User,
label: "Gestão de Usuários",
// Botão para página do editor de laudo
},
{
href: "#",
icon: User,
label: "Gestão de Médicos",
// Botão para a página de visualização de todos os pacientes
},
{
href: "#",
icon: Calendar,
label: "Configurações",
// Botão para página de consultas marcadas do médico atual
},
];
if (!managerData) {
return <div>Carregando...</div>;
}
return (
<div className="min-h-screen bg-gray-50 flex">
{/* Sidebar */}
<div className={`bg-white border-r border-gray-200 transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
<div className="p-4 border-b border-gray-200">
<div className="flex items-center justify-between">
{!sidebarCollapsed && (
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<div className="w-4 h-4 bg-white rounded-sm"></div>
</div>
<span className="font-semibold text-gray-900">Hospital System</span>
</div>
)}
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
</Button>
</div>
</div>
<nav className="flex-1 p-2 overflow-y-auto">
{menuItems.map((item) => {
const Icon = item.icon;
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
return (
<Link key={item.href} href={item.href}>
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-blue-50 text-blue-600 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50"}`}>
<Icon className="w-5 h-5 flex-shrink-0" />
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
</div>
</Link>
);
})}
</nav>
<div className="border-t p-4 mt-auto">
<div className="flex items-center space-x-3 mb-4">
<Avatar>
<AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{managerData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">{managerData.name}</p>
<p className="text-xs text-gray-500 truncate">{managerData.department}</p>
</div>
</div>
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
Sair
</Button>
</div>
</div>
{/* Main Content */}
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
{/* Header */}
<header className="bg-white border-b border-gray-200 px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1 max-w-md">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input placeholder="Buscar paciente" className="pl-10 bg-gray-50 border-gray-200" />
</div>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" className="relative">
<Bell className="w-5 h-5" />
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-red-500 text-white text-xs">1</Badge>
</Button>
</div>
</div>
</header>
{/* Page Content */}
<main className="flex-1 p-6">{children}</main>
</div>
{/* Logout confirmation dialog */}
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Confirmar Saída</DialogTitle>
<DialogDescription>Deseja realmente sair do sistema? Você precisará fazer login novamente para acessar sua conta.</DialogDescription>
</DialogHeader>
<DialogFooter className="flex gap-2">
<Button variant="outline" onClick={cancelLogout}>
Cancelar
</Button>
<Button variant="destructive" onClick={confirmLogout}>
<LogOut className="mr-2 h-4 w-4" />
Sair
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View File

@ -66,13 +66,13 @@ export default function SecretaryLayout({ children }: PatientLayoutProps) {
const menuItems = [ const menuItems = [
{ {
href: "#", href: "##",
icon: Home, icon: Home,
label: "Dashboard", label: "Dashboard",
// Botão para o dashboard da secretária // Botão para o dashboard da secretária
}, },
{ {
href: "#", href: "###",
icon: Calendar, icon: Calendar,
label: "Consultas", label: "Consultas",
// Botão para página de consultas marcadas // Botão para página de consultas marcadas

0
liraS2-patch-1 Normal file
View File

12
package-lock.json generated
View File

@ -62,13 +62,13 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.9", "@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22", "@types/node": "^22.18.6",
"@types/react": "^18", "@types/react": "^18.3.24",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"postcss": "^8.5", "postcss": "^8.5",
"tailwindcss": "^4.1.9", "tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3", "tw-animate-css": "1.3.3",
"typescript": "^5" "typescript": "^5.9.2"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@ -2031,9 +2031,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.18.5", "version": "22.18.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz",
"integrity": "sha512-g9BpPfJvxYBXUWI9bV37j6d6LTMNQ88hPwdWWUeYZnMhlo66FIg9gCc1/DZb15QylJSKwOZjwrckvOTWpOiChg==", "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@ -63,12 +63,12 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.9", "@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22", "@types/node": "^22.18.6",
"@types/react": "^18", "@types/react": "^18.3.24",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"postcss": "^8.5", "postcss": "^8.5",
"tailwindcss": "^4.1.9", "tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3", "tw-animate-css": "1.3.3",
"typescript": "^5" "typescript": "^5.9.2"
} }
} }

61
services/api.mjs Normal file
View File

@ -0,0 +1,61 @@
// váriaveis básicas
const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
var tempToken;
async function login() {
const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password", {
method: "POST",
headers: {
"Content-Type": "application/json",
"apikey": API_KEY, // valor fixo
},
body: JSON.stringify({ email: "riseup@popcode.com.br", password: "riseup" }),
});
const data = await response.json();
// salvar o token do usuário
localStorage.setItem("token", data.access_token);
return data;
}
await login()
async function request(endpoint, options = {}) {
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
...options.headers,
};
try {
const response = await fetch(`${BASE_URL}${endpoint}`, {
...options,
headers,
});
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Erro na requisição:", error);
throw error;
}
}
export const api = {
get: (endpoint) => request(endpoint, { method: "GET" }),
post: (endpoint, body) =>
request(endpoint, { method: "POST", body: JSON.stringify(body) }),
patch: (endpoint, body) =>
request(endpoint, { method: "PATCH", body: JSON.stringify(body) }),
delete: (endpoint) => request(endpoint, { method: "DELETE" }),
};

0
services/doctorsApi.mjs Normal file
View File

9
services/patientsApi.mjs Normal file
View File

@ -0,0 +1,9 @@
import { api } from "./api.mjs";
export const patientsService = {
list: () => api.get("/rest/v1/patients"),
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=eq.${id}`, data),
delete: (id) => api.delete(`/rest/v1/patients?id=eq.${id}`),
};