develop #83

Merged
M-Gabrielly merged 426 commits from develop into main 2025-12-04 04:13:15 +00:00
8 changed files with 1161 additions and 377 deletions
Showing only changes of commit e85fbdeb15 - Show all commits

View File

@ -12,12 +12,12 @@ import { Badge } from "@/components/ui/badge";
import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form";
import { listarMedicos, excluirMedico, Medico } from "@/lib/api"; import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, Medico } from "@/lib/api";
function normalizeMedico(m: any): Medico { function normalizeMedico(m: any): Medico {
return { return {
id: String(m.id ?? m.uuid ?? ""), id: String(m.id ?? m.uuid ?? ""),
nome: m.nome ?? m.full_name ?? "", // 👈 Supabase usa full_name full_name: m.full_name ?? m.nome ?? "", // 👈 Correção: usar full_name como padrão
nome_social: m.nome_social ?? m.social_name ?? null, nome_social: m.nome_social ?? m.social_name ?? null,
cpf: m.cpf ?? "", cpf: m.cpf ?? "",
rg: m.rg ?? m.document_number ?? null, rg: m.rg ?? m.document_number ?? null,
@ -39,6 +39,20 @@ function normalizeMedico(m: any): Medico {
dados_bancarios: m.dados_bancarios ?? null, dados_bancarios: m.dados_bancarios ?? null,
agenda_horario: m.agenda_horario ?? null, agenda_horario: m.agenda_horario ?? null,
valor_consulta: m.valor_consulta ?? null, valor_consulta: m.valor_consulta ?? null,
active: m.active ?? true,
cep: m.cep ?? "",
city: m.city ?? "",
complement: m.complement ?? null,
neighborhood: m.neighborhood ?? "",
number: m.number ?? "",
phone2: m.phone2 ?? null,
state: m.state ?? "",
street: m.street ?? "",
created_at: m.created_at ?? null,
created_by: m.created_by ?? null,
updated_at: m.updated_at ?? null,
updated_by: m.updated_by ?? null,
user_id: m.user_id ?? null,
}; };
} }
@ -50,33 +64,178 @@ export default function DoutoresPage() {
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null); const [editingId, setEditingId] = useState<string | null>(null);
const [viewingDoctor, setViewingDoctor] = useState<Medico | null>(null); const [viewingDoctor, setViewingDoctor] = useState<Medico | null>(null);
const [searchResults, setSearchResults] = useState<Medico[]>([]);
const [searchMode, setSearchMode] = useState(false);
const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
async function load() { async function load() {
setLoading(true); setLoading(true);
try { try {
const list = await listarMedicos({ limit: 50 }); const list = await listarMedicos({ limit: 50 });
setDoctors((list ?? []).map(normalizeMedico)); const normalized = (list ?? []).map(normalizeMedico);
console.log('🏥 Médicos carregados:', normalized);
setDoctors(normalized);
} finally { } finally {
setLoading(false); setLoading(false);
} }
} }
// Função para detectar se é um UUID válido
function isValidUUID(str: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(str);
}
// Função para buscar médicos no servidor
async function handleBuscarServidor(termoBusca?: string) {
const termo = (termoBusca || search).trim();
if (!termo) {
setSearchMode(false);
setSearchResults([]);
return;
}
console.log('🔍 Buscando médico por:', termo);
setLoading(true);
try {
// Se parece com UUID, tenta busca direta por ID
if (isValidUUID(termo)) {
console.log('📋 Detectado UUID, buscando por ID...');
try {
const medico = await buscarMedicoPorId(termo);
const normalizado = normalizeMedico(medico);
console.log('✅ Médico encontrado por ID:', normalizado);
setSearchResults([normalizado]);
setSearchMode(true);
return;
} catch (error) {
console.log('❌ Não encontrado por ID, tentando busca geral...');
}
}
// Busca geral
const resultados = await buscarMedicos(termo);
const normalizados = resultados.map(normalizeMedico);
console.log('📋 Resultados da busca geral:', normalizados);
setSearchResults(normalizados);
setSearchMode(true);
} catch (error) {
console.error('❌ Erro na busca:', error);
setSearchResults([]);
setSearchMode(true);
} finally {
setLoading(false);
}
}
// Handler para mudança no campo de busca com busca automática
function handleSearchChange(e: React.ChangeEvent<HTMLInputElement>) {
const valor = e.target.value;
setSearch(valor);
// Limpa o timeout anterior se existir
if (searchTimeout) {
clearTimeout(searchTimeout);
}
// Se limpar a busca, volta ao modo normal
if (!valor.trim()) {
setSearchMode(false);
setSearchResults([]);
return;
}
// Busca automática com debounce ajustável
// Para IDs (UUID) longos, faz busca no servidor
// Para busca parcial, usa apenas filtro local
const isLikeUUID = valor.includes('-') && valor.length > 10;
const shouldSearchServer = isLikeUUID || valor.length >= 3;
if (shouldSearchServer) {
const debounceTime = isLikeUUID ? 300 : 500;
const newTimeout = setTimeout(() => {
handleBuscarServidor(valor);
}, debounceTime);
setSearchTimeout(newTimeout);
} else {
// Para termos curtos, apenas usa filtro local
setSearchMode(false);
setSearchResults([]);
}
}
// Handler para Enter no campo de busca
function handleSearchKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') {
e.preventDefault();
handleBuscarServidor();
}
}
// Handler para o botão de busca
function handleClickBuscar() {
handleBuscarServidor();
}
useEffect(() => { useEffect(() => {
load(); load();
}, []); }, []);
const filtered = useMemo(() => { // Limpa o timeout quando o componente é desmontado
useEffect(() => {
return () => {
if (searchTimeout) {
clearTimeout(searchTimeout);
}
};
}, [searchTimeout]);
// Lista de médicos a exibir (busca ou filtro local)
const displayedDoctors = useMemo(() => {
console.log('🔍 Filtro - search:', search, 'searchMode:', searchMode, 'doctors:', doctors.length, 'searchResults:', searchResults.length);
// Se não tem busca, mostra todos os médicos
if (!search.trim()) return doctors; if (!search.trim()) return doctors;
const q = search.toLowerCase();
return doctors.filter((d) => { const q = search.toLowerCase().trim();
const byName = (d.nome || "").toLowerCase().includes(q); const qDigits = q.replace(/\D/g, "");
const byCrm = (d.crm || "").toLowerCase().includes(q);
// Se estamos em modo de busca (servidor), filtra os resultados da busca
const sourceList = searchMode ? searchResults : doctors;
console.log('🔍 Usando sourceList:', searchMode ? 'searchResults' : 'doctors', '- tamanho:', sourceList.length);
const filtered = sourceList.filter((d) => {
// Busca por nome
const byName = (d.full_name || "").toLowerCase().includes(q);
// Busca por CRM (remove formatação se necessário)
const byCrm = qDigits.length >= 3 && (d.crm || "").replace(/\D/g, "").includes(qDigits);
// Busca por ID (UUID completo ou parcial)
const byId = (d.id || "").toLowerCase().includes(q);
// Busca por email
const byEmail = (d.email || "").toLowerCase().includes(q);
// Busca por especialidade
const byEspecialidade = (d.especialidade || "").toLowerCase().includes(q); const byEspecialidade = (d.especialidade || "").toLowerCase().includes(q);
return byName || byCrm || byEspecialidade;
const match = byName || byCrm || byId || byEmail || byEspecialidade;
if (match) {
console.log('✅ Match encontrado:', d.full_name, d.id, 'por:', { byName, byCrm, byId, byEmail, byEspecialidade });
}
return match;
}); });
}, [doctors, search]);
console.log('🔍 Resultados filtrados:', filtered.length);
return filtered;
}, [doctors, search, searchMode, searchResults]);
function handleAdd() { function handleAdd() {
setEditingId(null); setEditingId(null);
@ -139,7 +298,7 @@ setDoctors((list ?? []).map(normalizeMedico));
<DoctorRegistrationForm <DoctorRegistrationForm
inline inline
mode={editingId ? "edit" : "create"} mode={editingId ? "edit" : "create"}
doctorId={editingId ? Number(editingId) : null} doctorId={editingId}
onSaved={handleSaved} onSaved={handleSaved}
onClose={() => setShowForm(false)} onClose={() => setShowForm(false)}
/> />
@ -156,15 +315,37 @@ setDoctors((list ?? []).map(normalizeMedico));
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex gap-2">
<div className="relative"> <div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" /> <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input <Input
className="pl-8 w-80" className="pl-8 w-80"
placeholder="Buscar por nome, CRM ou especialidade…" placeholder="Digite para buscar por ID, nome, CRM ou especialidade…"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={handleSearchChange}
onKeyDown={handleSearchKeyDown}
/> />
</div> </div>
<Button
variant="outline"
onClick={handleClickBuscar}
disabled={loading || !search.trim()}
>
Buscar
</Button>
{searchMode && (
<Button
variant="ghost"
onClick={() => {
setSearch("");
setSearchMode(false);
setSearchResults([]);
}}
>
Limpar
</Button>
)}
</div>
<Button onClick={handleAdd} disabled={loading}> <Button onClick={handleAdd} disabled={loading}>
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
Novo Médico Novo Médico
@ -190,10 +371,10 @@ setDoctors((list ?? []).map(normalizeMedico));
Carregando Carregando
</TableCell> </TableCell>
</TableRow> </TableRow>
) : filtered.length > 0 ? ( ) : displayedDoctors.length > 0 ? (
filtered.map((doctor) => ( displayedDoctors.map((doctor) => (
<TableRow key={doctor.id}> <TableRow key={doctor.id}>
<TableCell className="font-medium">{doctor.nome}</TableCell> <TableCell className="font-medium">{doctor.full_name}</TableCell>
<TableCell> <TableCell>
<Badge variant="outline">{doctor.especialidade}</Badge> <Badge variant="outline">{doctor.especialidade}</Badge>
</TableCell> </TableCell>
@ -247,13 +428,13 @@ setDoctors((list ?? []).map(normalizeMedico));
<DialogHeader> <DialogHeader>
<DialogTitle>Detalhes do Médico</DialogTitle> <DialogTitle>Detalhes do Médico</DialogTitle>
<DialogDescription> <DialogDescription>
Informações detalhadas de {viewingDoctor?.nome}. Informações detalhadas de {viewingDoctor?.full_name}.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Nome</Label> <Label className="text-right">Nome</Label>
<span className="col-span-3 font-medium">{viewingDoctor?.nome}</span> <span className="col-span-3 font-medium">{viewingDoctor?.full_name}</span>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Especialidade</Label> <Label className="text-right">Especialidade</Label>
@ -282,7 +463,7 @@ setDoctors((list ?? []).map(normalizeMedico));
)} )}
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
Mostrando {filtered.length} de {doctors.length} Mostrando {displayedDoctors.length} {searchMode ? 'resultado(s) da busca' : `de ${doctors.length}`}
</div> </div>
</div> </div>
); );

View File

@ -10,34 +10,29 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { MoreHorizontal, Plus, Search, Eye, Edit, Trash2, ArrowLeft } from "lucide-react"; import { MoreHorizontal, Plus, Search, Eye, Edit, Trash2, ArrowLeft } from "lucide-react";
import { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api"; import { Paciente, Endereco, listarPacientes, buscarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api";
import { PatientRegistrationForm } from "@/components/forms/patient-registration-form"; import { PatientRegistrationForm } from "@/components/forms/patient-registration-form";
function normalizePaciente(p: any): Paciente { function normalizePaciente(p: any): Paciente {
const endereco: Endereco = {
cep: p.endereco?.cep ?? p.cep ?? "",
logradouro: p.endereco?.logradouro ?? p.street ?? "",
numero: p.endereco?.numero ?? p.number ?? "",
complemento: p.endereco?.complemento ?? p.complement ?? "",
bairro: p.endereco?.bairro ?? p.neighborhood ?? "",
cidade: p.endereco?.cidade ?? p.city ?? "",
estado: p.endereco?.estado ?? p.state ?? "",
};
return { return {
id: String(p.id ?? p.uuid ?? p.paciente_id ?? ""), id: String(p.id ?? p.uuid ?? p.paciente_id ?? ""),
nome: p.full_name ?? "", // 👈 troca nome → full_name full_name: p.full_name ?? p.name ?? p.nome ?? "",
nome_social: p.social_name ?? null, // 👈 Supabase usa social_name social_name: p.social_name ?? p.nome_social ?? null,
cpf: p.cpf ?? "", cpf: p.cpf ?? "",
rg: p.rg ?? p.document_number ?? null, // 👈 às vezes vem como document_number rg: p.rg ?? p.document_number ?? null,
sexo: p.sexo ?? p.sex ?? null, // 👈 Supabase usa sex sex: p.sex ?? p.sexo ?? null,
data_nascimento: p.data_nascimento ?? p.birth_date ?? null, birth_date: p.birth_date ?? p.data_nascimento ?? null,
telefone: p.telefone ?? p.phone_mobile ?? "", phone_mobile: p.phone_mobile ?? p.telefone ?? "",
email: p.email ?? "", email: p.email ?? "",
endereco, cep: p.cep ?? "",
observacoes: p.observacoes ?? p.notes ?? null, street: p.street ?? p.logradouro ?? "",
foto_url: p.foto_url ?? null, number: p.number ?? p.numero ?? "",
complement: p.complement ?? p.complemento ?? "",
neighborhood: p.neighborhood ?? p.bairro ?? "",
city: p.city ?? p.cidade ?? "",
state: p.state ?? p.estado ?? "",
notes: p.notes ?? p.observacoes ?? null,
}; };
} }
@ -56,7 +51,12 @@ export default function PacientesPage() {
try { try {
setLoading(true); setLoading(true);
const data = await listarPacientes({ page: 1, limit: 20 }); const data = await listarPacientes({ page: 1, limit: 20 });
setPatients((data ?? []).map(normalizePaciente));
if (Array.isArray(data)) {
setPatients(data.map(normalizePaciente));
} else {
setPatients([]);
}
setError(null); setError(null);
} catch (e: any) { } catch (e: any) {
setPatients([]); setPatients([]);
@ -72,13 +72,23 @@ export default function PacientesPage() {
const filtered = useMemo(() => { const filtered = useMemo(() => {
if (!search.trim()) return patients; if (!search.trim()) return patients;
const q = search.toLowerCase(); const q = search.toLowerCase().trim();
const qDigits = q.replace(/\D/g, ""); const qDigits = q.replace(/\D/g, "");
return patients.filter((p) => { return patients.filter((p) => {
const byName = (p.nome || "").toLowerCase().includes(q); // Busca por nome
const byCPF = (p.cpf || "").replace(/\D/g, "").includes(qDigits); const byName = (p.full_name || "").toLowerCase().includes(q);
const byId = String(p.id || "").includes(qDigits);
return byName || byCPF || byId; // Busca por CPF (remove formatação)
const byCPF = qDigits.length >= 3 && (p.cpf || "").replace(/\D/g, "").includes(qDigits);
// Busca por ID (UUID completo ou parcial)
const byId = (p.id || "").toLowerCase().includes(q);
// Busca por email
const byEmail = (p.email || "").toLowerCase().includes(q);
return byName || byCPF || byId || byEmail;
}); });
}, [patients, search]); }, [patients, search]);
@ -122,25 +132,33 @@ export default function PacientesPage() {
const q = search.trim(); const q = search.trim();
if (!q) return loadAll(); if (!q) return loadAll();
if (/^\d+$/.test(q)) {
try { try {
setLoading(true); setLoading(true);
setError(null);
// Se parece com ID (UUID), busca diretamente
if (q.includes('-') && q.length > 10) {
const one = await buscarPacientePorId(q); const one = await buscarPacientePorId(q);
setPatients(one ? [normalizePaciente(one)] : []); setPatients(one ? [normalizePaciente(one)] : []);
setError(one ? null : "Paciente não encontrado."); setError(one ? null : "Paciente não encontrado.");
} catch (e: any) { // Limpa o campo de busca para que o filtro não interfira
setPatients([]); setSearch("");
setError(e?.message || "Paciente não encontrado.");
} finally {
setLoading(false);
}
return; return;
} }
// Para outros termos, usa busca avançada
const results = await buscarPacientes(q);
setPatients(results.map(normalizePaciente));
setError(results.length === 0 ? "Nenhum paciente encontrado." : null);
// Limpa o campo de busca para que o filtro não interfira
setSearch("");
await loadAll(); } catch (e: any) {
setTimeout(() => setSearch(q), 0); setPatients([]);
setError(e?.message || "Erro na busca.");
} finally {
setLoading(false);
}
} }
if (loading) return <p>Carregando pacientes...</p>; if (loading) return <p>Carregando pacientes...</p>;
@ -159,7 +177,7 @@ export default function PacientesPage() {
<PatientRegistrationForm <PatientRegistrationForm
inline inline
mode={editingId ? "edit" : "create"} mode={editingId ? "edit" : "create"}
patientId={editingId ? Number(editingId) : null} patientId={editingId}
onSaved={handleSaved} onSaved={handleSaved}
onClose={() => setShowForm(false)} onClose={() => setShowForm(false)}
/> />
@ -210,11 +228,11 @@ export default function PacientesPage() {
{filtered.length > 0 ? ( {filtered.length > 0 ? (
filtered.map((p) => ( filtered.map((p) => (
<TableRow key={p.id}> <TableRow key={p.id}>
<TableCell className="font-medium">{p.nome || "(sem nome)"}</TableCell> <TableCell className="font-medium">{p.full_name || "(sem nome)"}</TableCell>
<TableCell>{p.cpf || "-"}</TableCell> <TableCell>{p.cpf || "-"}</TableCell>
<TableCell>{p.telefone || "-"}</TableCell> <TableCell>{p.phone_mobile || "-"}</TableCell>
<TableCell>{p.endereco?.cidade || "-"}</TableCell> <TableCell>{p.city || "-"}</TableCell>
<TableCell>{p.endereco?.estado || "-"}</TableCell> <TableCell>{p.state || "-"}</TableCell>
<TableCell> <TableCell>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -258,13 +276,13 @@ export default function PacientesPage() {
<DialogHeader> <DialogHeader>
<DialogTitle>Detalhes do Paciente</DialogTitle> <DialogTitle>Detalhes do Paciente</DialogTitle>
<DialogDescription> <DialogDescription>
Informações detalhadas de {viewingPatient.nome}. Informações detalhadas de {viewingPatient.full_name}.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Nome</Label> <Label className="text-right">Nome</Label>
<span className="col-span-3 font-medium">{viewingPatient.nome}</span> <span className="col-span-3 font-medium">{viewingPatient.full_name}</span>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">CPF</Label> <Label className="text-right">CPF</Label>
@ -272,17 +290,17 @@ export default function PacientesPage() {
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Telefone</Label> <Label className="text-right">Telefone</Label>
<span className="col-span-3">{viewingPatient.telefone}</span> <span className="col-span-3">{viewingPatient.phone_mobile}</span>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Endereço</Label> <Label className="text-right">Endereço</Label>
<span className="col-span-3"> <span className="col-span-3">
{`${viewingPatient.endereco?.logradouro || ''}, ${viewingPatient.endereco?.numero || ''} - ${viewingPatient.endereco?.bairro || ''}, ${viewingPatient.endereco?.cidade || ''} - ${viewingPatient.endereco?.estado || ''}`} {`${viewingPatient.street || ''}, ${viewingPatient.number || ''} - ${viewingPatient.neighborhood || ''}, ${viewingPatient.city || ''} - ${viewingPatient.state || ''}`}
</span> </span>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Observações</Label> <Label className="text-right">Observações</Label>
<span className="col-span-3">{viewingPatient.observacoes || "Nenhuma"}</span> <span className="col-span-3">{viewingPatient.notes || "Nenhuma"}</span>
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>

View File

@ -7,6 +7,7 @@ import "react-quill/dist/quill.snow.css";
import Link from "next/link"; import Link from "next/link";
import ProtectedRoute from "@/components/ProtectedRoute"; import ProtectedRoute from "@/components/ProtectedRoute";
import { useAuth } from "@/hooks/useAuth"; import { useAuth } from "@/hooks/useAuth";
import { buscarPacientes } from "@/lib/api";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -566,9 +567,143 @@ const ProfissionalPage = () => {
}; };
const renderPacientesSection = () => ( const renderPacientesSection = () => {
// Estados para busca de pacientes
const [buscaPaciente, setBuscaPaciente] = useState("");
const [pacientesBusca, setPacientesBusca] = useState<any[]>([]);
const [carregandoBusca, setCarregandoBusca] = useState(false);
const [erroBusca, setErroBusca] = useState<string | null>(null);
// Função para buscar pacientes
const handleBuscarPaciente = async () => {
if (!buscaPaciente.trim()) {
setPacientesBusca([]);
setErroBusca(null);
return;
}
setCarregandoBusca(true);
setErroBusca(null);
try {
// Importa a função de busca
const { buscarPacientes } = await import("@/lib/api");
const resultados = await buscarPacientes(buscaPaciente.trim());
if (resultados.length === 0) {
setErroBusca("Nenhum paciente encontrado com os critérios informados.");
setPacientesBusca([]);
} else {
// Transforma os dados da API para o formato usado no componente
const pacientesFormatados = resultados.map(p => ({
nome: p.full_name || "Nome não informado",
cpf: p.cpf || "CPF não informado",
idade: p.birth_date ? new Date().getFullYear() - new Date(p.birth_date).getFullYear() : "N/A",
statusLaudo: "Pendente", // Status padrão
id: p.id
}));
setPacientesBusca(pacientesFormatados);
setErroBusca(null);
}
} catch (error: any) {
console.error("Erro ao buscar pacientes:", error);
setErroBusca(error.message || "Erro ao buscar pacientes. Tente novamente.");
setPacientesBusca([]);
} finally {
setCarregandoBusca(false);
}
};
const handleLimparBusca = () => {
setBuscaPaciente("");
setPacientesBusca([]);
setErroBusca(null);
};
return (
<div className="bg-white shadow-md rounded-lg p-6"> <div className="bg-white shadow-md rounded-lg p-6">
<h2 className="text-2xl font-bold mb-4">Gerenciamento de Pacientes</h2> <h2 className="text-2xl font-bold mb-4">Gerenciamento de Pacientes</h2>
{/* Campo de busca */}
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
<h3 className="text-lg font-semibold mb-3">Buscar Paciente</h3>
<div className="flex gap-2">
<div className="flex-1">
<Input
placeholder="Digite ID, CPF, nome ou email do paciente..."
value={buscaPaciente}
onChange={(e) => setBuscaPaciente(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleBuscarPaciente()}
className="w-full"
/>
</div>
<Button
onClick={handleBuscarPaciente}
disabled={carregandoBusca}
className="flex items-center gap-2"
>
{carregandoBusca ? (
<>
<div className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div>
Buscando...
</>
) : (
<>
<User className="h-4 w-4" />
Buscar
</>
)}
</Button>
{(buscaPaciente || pacientesBusca.length > 0 || erroBusca) && (
<Button
variant="outline"
onClick={handleLimparBusca}
className="flex items-center gap-2"
>
<X className="h-4 w-4" />
Limpar
</Button>
)}
</div>
{/* Resultados da busca */}
{erroBusca && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-700 text-sm">{erroBusca}</p>
</div>
)}
{pacientesBusca.length > 0 && (
<div className="mt-4">
<h4 className="text-md font-medium mb-2">Resultados da busca ({pacientesBusca.length}):</h4>
<div className="space-y-2">
{pacientesBusca.map((paciente, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-white border rounded-lg hover:shadow-sm">
<div>
<p className="font-medium">{paciente.nome}</p>
<p className="text-sm text-gray-600">CPF: {paciente.cpf} Idade: {paciente.idade} anos</p>
</div>
<Button
size="sm"
onClick={() => {
handleAbrirProntuario(paciente);
setActiveSection('prontuario');
}}
className="flex items-center gap-2"
>
<FolderOpen className="h-4 w-4" />
Abrir Prontuário
</Button>
</div>
))}
</div>
</div>
)}
</div>
{/* Tabela de pacientes padrão */}
<div>
<h3 className="text-lg font-semibold mb-3">Pacientes Recentes</h3>
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
@ -612,7 +747,9 @@ const ProfissionalPage = () => {
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
</div>
); );
};
const renderProntuarioSection = () => ( const renderProntuarioSection = () => (

View File

@ -1,6 +1,6 @@
"use client" "use client"
import { Bell, Search, ChevronDown } from "lucide-react" import { Bell, ChevronDown } from "lucide-react"
import { useAuth } from "@/hooks/useAuth" import { useAuth } from "@/hooks/useAuth"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
@ -40,11 +40,6 @@ export function PagesHeader({ title = "", subtitle = "" }: { title?: string, sub
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input placeholder="Buscar paciente" className="pl-10 w-64" />
</div>
<Button variant="ghost" size="icon"> <Button variant="ghost" size="icon">
<Bell className="h-4 w-4" /> <Bell className="h-4 w-4" />
</Button> </Button>

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { buscarPacientePorId } from "@/lib/api";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -21,8 +22,10 @@ import {
listarAnexosMedico, listarAnexosMedico,
adicionarAnexoMedico, adicionarAnexoMedico,
removerAnexoMedico, removerAnexoMedico,
MedicoInput, MedicoInput, // 👈 importado do lib/api
Medico, // 👈 adicionado import do tipo Medico
} from "@/lib/api"; } from "@/lib/api";
;
import { buscarCepAPI } from "@/lib/api"; import { buscarCepAPI } from "@/lib/api";
@ -39,39 +42,16 @@ type DadosBancarios = {
tipo_conta: string; tipo_conta: string;
}; };
export type Medico = {
id: string;
nome?: string;
nome_social?: string | null;
cpf?: string;
rg?: string | null;
sexo?: string | null;
data_nascimento?: string | null;
telefone?: string;
celular?: string;
contato_emergencia?: string;
email?: string;
crm?: string;
estado_crm?: string;
rqe?: string;
formacao_academica?: FormacaoAcademica[];
curriculo_url?: string | null;
especialidade?: string;
observacoes?: string | null;
foto_url?: string | null;
tipo_vinculo?: string;
dados_bancarios?: DadosBancarios;
agenda_horario?: string;
valor_consulta?: number | string;
};
type Mode = "create" | "edit"; type Mode = "create" | "edit";
export interface DoctorRegistrationFormProps { export interface DoctorRegistrationFormProps {
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
doctorId?: number | null; doctorId?: string | number | null;
inline?: boolean; inline?: boolean;
mode?: Mode; mode?: Mode;
onSaved?: (medico: Medico) => void; onSaved?: (medico: Medico) => void;
@ -80,7 +60,7 @@ export interface DoctorRegistrationFormProps {
type FormData = { type FormData = {
photo: File | null; photo: File | null;
nome: string; full_name: string; // Substitua 'nome' por 'full_name'
nome_social: string; nome_social: string;
crm: string; crm: string;
estado_crm: string; estado_crm: string;
@ -107,14 +87,13 @@ type FormData = {
anexos: File[]; anexos: File[];
tipo_vinculo: string; tipo_vinculo: string;
dados_bancarios: DadosBancarios; dados_bancarios: DadosBancarios;
agenda_horario: string; agenda_horario: string;
valor_consulta: string; valor_consulta: string;
}; };
const initial: FormData = { const initial: FormData = {
photo: null, photo: null,
nome: "", full_name: "",
nome_social: "", nome_social: "",
crm: "", crm: "",
estado_crm: "", estado_crm: "",
@ -128,7 +107,7 @@ const initial: FormData = {
data_nascimento: "", data_nascimento: "",
email: "", email: "",
telefone: "", telefone: "",
celular: "", celular: "", // Aqui, 'celular' pode ser 'phone_mobile'
contato_emergencia: "", contato_emergencia: "",
cep: "", cep: "",
logradouro: "", logradouro: "",
@ -152,6 +131,7 @@ const initial: FormData = {
export function DoctorRegistrationForm({ export function DoctorRegistrationForm({
open = true, open = true,
onOpenChange, onOpenChange,
@ -175,46 +155,78 @@ export function DoctorRegistrationForm({
let alive = true; let alive = true;
async function load() { async function load() {
if (mode === "edit" && doctorId) { if (mode === "edit" && doctorId) {
const medico = await buscarMedicoPorId(doctorId); try {
if (!alive) return; console.log("[DoctorForm] Carregando médico ID:", doctorId);
setForm({ const medico = await buscarMedicoPorId(String(doctorId));
photo: null, console.log("[DoctorForm] Dados recebidos do API:", medico);
nome: medico.nome ?? "", console.log("[DoctorForm] Campos principais:", {
nome_social: medico.nome_social ?? "", full_name: medico.full_name,
crm: medico.crm ?? "", crm: medico.crm,
estado_crm: medico.estado_crm ?? "", especialidade: medico.especialidade,
rqe: medico.rqe ?? "", specialty: (medico as any).specialty,
formacao_academica: medico.formacao_academica ?? [], cpf: medico.cpf,
curriculo: null, email: medico.email
especialidade: medico.especialidade ?? "",
cpf: medico.cpf ?? "",
rg: medico.rg ?? "",
sexo: medico.sexo ?? "",
data_nascimento: medico.data_nascimento ?? "",
email: medico.email ?? "",
telefone: medico.telefone ?? "",
celular: medico.celular ?? "",
contato_emergencia: medico.contato_emergencia ?? "",
cep: "",
logradouro: "",
numero: "",
complemento: "",
bairro: "",
cidade: "",
estado: "",
observacoes: medico.observacoes ?? "",
anexos: [],
tipo_vinculo: medico.tipo_vinculo ?? "",
dados_bancarios: medico.dados_bancarios ?? { banco: "", agencia: "", conta: "", tipo_conta: "" },
agenda_horario: medico.agenda_horario ?? "",
valor_consulta: medico.valor_consulta ? String(medico.valor_consulta) : "",
}); });
console.log("[DoctorForm] Verificando especialidade:", {
'medico.especialidade': medico.especialidade,
'medico.specialty': (medico as any).specialty,
'typeof especialidade': typeof medico.especialidade,
'especialidade length': medico.especialidade?.length
});
if (!alive) return;
// Busca a especialidade em diferentes campos possíveis
const especialidade = medico.especialidade ||
(medico as any).specialty ||
(medico as any).speciality ||
"";
console.log('🎯 Especialidade encontrada:', especialidade);
const formData = {
photo: null,
full_name: String(medico.full_name || ""),
nome_social: String(medico.nome_social || ""),
crm: String(medico.crm || ""),
estado_crm: String(medico.estado_crm || ""),
rqe: String(medico.rqe || ""),
formacao_academica: Array.isArray(medico.formacao_academica) ? medico.formacao_academica : [],
curriculo: null,
especialidade: String(especialidade),
cpf: String(medico.cpf || ""),
rg: String(medico.rg || ""),
sexo: String(medico.sexo || ""),
data_nascimento: String(medico.data_nascimento || ""),
email: String(medico.email || ""),
telefone: String(medico.telefone || ""),
celular: String(medico.celular || ""),
contato_emergencia: String(medico.contato_emergencia || ""),
cep: String(medico.cep || ""),
logradouro: String(medico.street || ""),
numero: String(medico.number || ""),
complemento: String(medico.complement || ""),
bairro: String(medico.neighborhood || ""),
cidade: String(medico.city || ""),
estado: String(medico.state || ""),
observacoes: String(medico.observacoes || ""),
anexos: [],
tipo_vinculo: String(medico.tipo_vinculo || ""),
dados_bancarios: medico.dados_bancarios || { banco: "", agencia: "", conta: "", tipo_conta: "" },
agenda_horario: String(medico.agenda_horario || ""),
valor_consulta: medico.valor_consulta ? String(medico.valor_consulta) : "",
};
console.log("[DoctorForm] Dados do formulário preparados:", formData);
setForm(formData);
try { try {
const list = await listarAnexosMedico(doctorId); const list = await listarAnexosMedico(String(doctorId));
setServerAnexos(list ?? []); setServerAnexos(list ?? []);
} catch {} } catch (err) {
console.error("[DoctorForm] Erro ao carregar anexos:", err);
}
} catch (err) {
console.error("[DoctorForm] Erro ao carregar médico:", err);
}
} }
} }
load(); load();
@ -228,6 +240,7 @@ export function DoctorRegistrationForm({
} }
function addFormacao() { function addFormacao() {
setField("formacao_academica", [ setField("formacao_academica", [
...form.formacao_academica, ...form.formacao_academica,
@ -301,74 +314,90 @@ export function DoctorRegistrationForm({
function validateLocal(): boolean { function validateLocal(): boolean {
const e: Record<string, string> = {}; const e: Record<string, string> = {};
if (!form.nome.trim()) e.nome = "Nome é obrigatório";
if (!form.full_name.trim()) e.full_name = "Nome é obrigatório";
if (!form.cpf.trim()) e.cpf = "CPF é obrigatório"; if (!form.cpf.trim()) e.cpf = "CPF é obrigatório";
if (!form.crm.trim()) e.crm = "CRM é obrigatório"; if (!form.crm.trim()) e.crm = "CRM é obrigatório";
if (!form.especialidade.trim()) e.especialidade = "Especialidade é obrigatória"; if (!form.especialidade.trim()) e.especialidade = "Especialidade é obrigatória";
if (!form.cep.trim()) e.cep = "CEP é obrigatório"; // Verifique se o CEP está preenchido
if (!form.bairro.trim()) e.bairro = "Bairro é obrigatório"; // Verifique se o bairro está preenchido
if (!form.cidade.trim()) e.cidade = "Cidade é obrigatória"; // Verifique se a cidade está preenchida
setErrors(e); setErrors(e);
return Object.keys(e).length === 0; return Object.keys(e).length === 0;
} }
async function handleSubmit(ev: React.FormEvent) { async function handleSubmit(ev: React.FormEvent) {
ev.preventDefault(); ev.preventDefault();
if (!validateLocal()) return; console.log("Submitting the form..."); // Verifique se a função está sendo chamada
if (!validateLocal()) {
console.log("Validation failed");
return; // Se a validação falhar, saia da função.
}
setSubmitting(true); setSubmitting(true);
setErrors((e) => ({ ...e, submit: "" })); setErrors((e) => ({ ...e, submit: "" }));
try {
const payload: MedicoInput = { const payload: MedicoInput = {
nome: form.nome, user_id: null,
nome_social: form.nome_social || null, crm: form.crm || "",
cpf: form.cpf || null, crm_uf: form.estado_crm || "",
specialty: form.especialidade || "",
full_name: form.full_name || "",
cpf: form.cpf || "",
email: form.email || "",
phone_mobile: form.celular || "",
phone2: form.telefone || null,
cep: form.cep || "",
street: form.logradouro || "",
number: form.numero || "",
complement: form.complemento || undefined,
neighborhood: form.bairro || undefined,
city: form.cidade || "",
state: form.estado || "",
birth_date: form.data_nascimento || null,
rg: form.rg || null, rg: form.rg || null,
sexo: form.sexo || null, active: true,
data_nascimento: form.data_nascimento || null, created_by: null,
telefone: form.telefone || null, updated_by: null,
celular: form.celular || null,
contato_emergencia: form.contato_emergencia || null,
email: form.email || null,
crm: form.crm,
estado_crm: form.estado_crm || null,
rqe: form.rqe || null,
formacao_academica: form.formacao_academica ?? [],
curriculo_url: null,
especialidade: form.especialidade,
observacoes: form.observacoes || null,
tipo_vinculo: form.tipo_vinculo || null,
dados_bancarios: form.dados_bancarios ?? null,
agenda_horario: form.agenda_horario || null,
valor_consulta: form.valor_consulta || null,
}; };
// Validação dos campos obrigatórios
const requiredFields = ['crm', 'crm_uf', 'specialty', 'full_name', 'cpf', 'email', 'phone_mobile', 'cep', 'street', 'number', 'city', 'state'];
const missingFields = requiredFields.filter(field => !payload[field as keyof MedicoInput]);
if (missingFields.length > 0) {
console.warn('⚠️ Campos obrigatórios vazios:', missingFields);
}
console.log("📤 Payload being sent:", payload);
console.log("🔧 Mode:", mode, "DoctorId:", doctorId);
try {
if (mode === "edit" && !doctorId) {
throw new Error("ID do médico não fornecido para edição");
}
const saved = mode === "create" const saved = mode === "create"
? await criarMedico(payload) ? await criarMedico(payload)
: await atualizarMedico(doctorId as number, payload); : await atualizarMedico(String(doctorId), payload);
const medicoId = saved.id; console.log("✅ Médico salvo com sucesso:", saved);
if (form.photo) {
try {
await uploadFotoMedico(medicoId, form.photo);
} catch (e) {
console.warn("Falha ao enviar foto:", e);
}
}
if (form.anexos?.length) {
for (const f of form.anexos) {
try {
await adicionarAnexoMedico(medicoId, f);
} catch (e) {
console.warn("Falha ao enviar anexo:", f.name, e);
}
}
}
onSaved?.(saved); onSaved?.(saved);
if (inline) onClose?.(); setSubmitting(false);
else onOpenChange?.(false);
} catch (err: any) { } catch (err: any) {
console.error("❌ Erro ao salvar médico:", err);
console.error("❌ Detalhes do erro:", {
message: err?.message,
status: err?.status,
stack: err?.stack
});
setErrors((e) => ({ ...e, submit: err?.message || "Erro ao salvar médico" })); setErrors((e) => ({ ...e, submit: err?.message || "Erro ao salvar médico" }));
} finally { } finally {
setSubmitting(false); setSubmitting(false);
@ -376,6 +405,10 @@ export function DoctorRegistrationForm({
} }
function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) { function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) {
const f = e.target.files?.[0]; const f = e.target.files?.[0];
if (!f) return; if (!f) return;
@ -449,8 +482,10 @@ export function DoctorRegistrationForm({
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label>Nome *</Label> <Label>Nome *</Label>
<Input value={form.nome} onChange={(e) => setField("nome", e.target.value)} className={errors.nome ? "border-destructive" : ""} /> <Input value={form.full_name} onChange={(e) => setField("full_name", e.target.value)} />
{errors.nome && <p className="text-sm text-destructive">{errors.nome}</p>}
{errors.full_name && <p className="text-sm text-destructive">{errors.full_name}</p>}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Nome Social</Label> <Label>Nome Social</Label>
@ -473,7 +508,11 @@ export function DoctorRegistrationForm({
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label>Especialidade *</Label> <Label>Especialidade *</Label>
<Input value={form.especialidade} onChange={(e) => setField("especialidade", e.target.value)} className={errors.especialidade ? "border-destructive" : ""} /> <Input
value={form.especialidade} // Mantenha o nome no form como 'especialidade'
onChange={(e) => setField("especialidade", e.target.value)} // Envia o valor correto
className={errors.especialidade ? "border-destructive" : ""}
/>
{errors.especialidade && <p className="text-sm text-destructive">{errors.especialidade}</p>} {errors.especialidade && <p className="text-sm text-destructive">{errors.especialidade}</p>}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@ -482,6 +521,7 @@ export function DoctorRegistrationForm({
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Currículo</Label> <Label>Currículo</Label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -629,6 +669,7 @@ export function DoctorRegistrationForm({
<Label>E-mail</Label> <Label>E-mail</Label>
<Input value={form.email} onChange={(e) => setField("email", e.target.value)} /> <Input value={form.email} onChange={(e) => setField("email", e.target.value)} />
</div> </div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label>Telefone</Label> <Label>Telefone</Label>
<Input <Input
@ -637,6 +678,16 @@ export function DoctorRegistrationForm({
placeholder="(XX) XXXXX-XXXX" placeholder="(XX) XXXXX-XXXX"
/> />
</div> </div>
<div className="space-y-2">
<Label>Celular</Label>
<Input
value={form.celular}
onChange={(e) => setField("celular", formatPhone(e.target.value))}
placeholder="(XX) XXXXX-XXXX"
/>
</div>
</div>
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
@ -703,11 +754,14 @@ export function DoctorRegistrationForm({
<div className="space-y-2"> <div className="space-y-2">
<Label>Agenda/Horário</Label> <Label>Agenda/Horário</Label>
<Textarea // Dentro do form, apenas exiba o campo se precisar dele visualmente, mas não envie
<textarea
value={form.agenda_horario} value={form.agenda_horario}
onChange={(e) => setField("agenda_horario", e.target.value)} onChange={(e) => setField("agenda_horario", e.target.value)}
placeholder="Descreva os dias e horários de atendimento" placeholder="Descreva os dias e horários de atendimento"
disabled={true} // Torne o campo apenas visual, sem enviar
/> />
</div> </div>
<div className="space-y-4"> <div className="space-y-4">

View File

@ -37,7 +37,7 @@ type Mode = "create" | "edit";
export interface PatientRegistrationFormProps { export interface PatientRegistrationFormProps {
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
patientId?: number | null; patientId?: string | number | null;
inline?: boolean; inline?: boolean;
mode?: Mode; mode?: Mode;
onSaved?: (paciente: Paciente) => void; onSaved?: (paciente: Paciente) => void;
@ -51,7 +51,7 @@ type FormData = {
cpf: string; cpf: string;
rg: string; rg: string;
sexo: string; sexo: string;
data_nascimento: string; birth_date: string; // 👈 corrigido
email: string; email: string;
telefone: string; telefone: string;
cep: string; cep: string;
@ -72,7 +72,7 @@ const initial: FormData = {
cpf: "", cpf: "",
rg: "", rg: "",
sexo: "", sexo: "",
data_nascimento: "", birth_date: "", // 👈 corrigido
email: "", email: "",
telefone: "", telefone: "",
cep: "", cep: "",
@ -86,6 +86,8 @@ const initial: FormData = {
anexos: [], anexos: [],
}; };
export function PatientRegistrationForm({ export function PatientRegistrationForm({
open = true, open = true,
onOpenChange, onOpenChange,
@ -110,30 +112,33 @@ export function PatientRegistrationForm({
async function load() { async function load() {
if (mode !== "edit" || patientId == null) return; if (mode !== "edit" || patientId == null) return;
try { try {
console.log("[PatientForm] Carregando paciente ID:", patientId);
const p = await buscarPacientePorId(String(patientId)); const p = await buscarPacientePorId(String(patientId));
console.log("[PatientForm] Dados recebidos:", p);
setForm((s) => ({ setForm((s) => ({
...s, ...s,
nome: p.nome || "", nome: p.full_name || "", // 👈 trocar nome → full_name
nome_social: p.nome_social || "", nome_social: p.social_name || "",
cpf: p.cpf || "", cpf: p.cpf || "",
rg: p.rg || "", rg: p.rg || "",
sexo: p.sexo || "", sexo: p.sex || "",
data_nascimento: (p.data_nascimento as string) || "", birth_date: p.birth_date || "", // 👈 trocar data_nascimento → birth_date
telefone: p.telefone || "", telefone: p.phone_mobile || "",
email: p.email || "", email: p.email || "",
cep: p.endereco?.cep || "", cep: p.cep || "",
logradouro: p.endereco?.logradouro || "", logradouro: p.street || "",
numero: p.endereco?.numero || "", numero: p.number || "",
complemento: p.endereco?.complemento || "", complemento: p.complement || "",
bairro: p.endereco?.bairro || "", bairro: p.neighborhood || "",
cidade: p.endereco?.cidade || "", cidade: p.city || "",
estado: p.endereco?.estado || "", estado: p.state || "",
observacoes: p.observacoes || "", observacoes: p.notes || "",
})); }));
const ax = await listarAnexos(String(patientId)).catch(() => []); const ax = await listarAnexos(String(patientId)).catch(() => []);
setServerAnexos(Array.isArray(ax) ? ax : []); setServerAnexos(Array.isArray(ax) ? ax : []);
} catch { } catch (err) {
console.error("[PatientForm] Erro ao carregar paciente:", err);
} }
} }
load(); load();
@ -187,27 +192,27 @@ export function PatientRegistrationForm({
function toPayload(): PacienteInput { function toPayload(): PacienteInput {
return { return {
nome: form.nome, full_name: form.nome, // 👈 troca 'nome' por 'full_name'
nome_social: form.nome_social || null, social_name: form.nome_social || null,
cpf: form.cpf, cpf: form.cpf,
rg: form.rg || null, rg: form.rg || null,
sexo: form.sexo || null, sex: form.sexo || null,
data_nascimento: form.data_nascimento || null, birth_date: form.birth_date || null, // 👈 troca data_nascimento → birth_date
telefone: form.telefone || null, phone_mobile: form.telefone || null,
email: form.email || null, email: form.email || null,
endereco: { cep: form.cep || null,
cep: form.cep || undefined, street: form.logradouro || null,
logradouro: form.logradouro || undefined, number: form.numero || null,
numero: form.numero || undefined, complement: form.complemento || null,
complemento: form.complemento || undefined, neighborhood: form.bairro || null,
bairro: form.bairro || undefined, city: form.cidade || null,
cidade: form.cidade || undefined, state: form.estado || null,
estado: form.estado || undefined, notes: form.observacoes || null,
},
observacoes: form.observacoes || null,
}; };
} }
async function handleSubmit(ev: React.FormEvent) { async function handleSubmit(ev: React.FormEvent) {
ev.preventDefault(); ev.preventDefault();
if (!validateLocal()) return; if (!validateLocal()) return;
@ -418,7 +423,8 @@ export function PatientRegistrationForm({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Data de Nascimento</Label> <Label>Data de Nascimento</Label>
<Input type="date" value={form.data_nascimento} onChange={(e) => setField("data_nascimento", e.target.value)} /> <Input type="date" value={form.birth_date} onChange={(e) => setField("birth_date", e.target.value)} />
</div> </div>
</div> </div>
</CardContent> </CardContent>

View File

@ -26,32 +26,44 @@ export type Endereco = {
// ===== PACIENTES ===== // ===== PACIENTES =====
export type Paciente = { export type Paciente = {
id: string; id: string;
nome?: string; full_name: string;
nome_social?: string | null; social_name?: string | null;
cpf?: string; cpf?: string;
rg?: string | null; rg?: string | null;
sexo?: string | null; sex?: string | null;
data_nascimento?: string | null; birth_date?: string | null;
telefone?: string; phone_mobile?: string;
email?: string; email?: string;
endereco?: Endereco; cep?: string | null;
observacoes?: string | null; street?: string | null;
foto_url?: string | null; number?: string | null;
complement?: string | null;
neighborhood?: string | null;
city?: string | null;
state?: string | null;
notes?: string | null;
}; };
export type PacienteInput = { export type PacienteInput = {
nome: string; full_name: string;
nome_social?: string | null; social_name?: string | null;
cpf: string; cpf: string;
rg?: string | null; rg?: string | null;
sexo?: string | null; sex?: string | null;
data_nascimento?: string | null; birth_date?: string | null;
telefone?: string | null; phone_mobile?: string | null;
email?: string | null; email?: string | null;
endereco?: Endereco; cep?: string | null;
observacoes?: string | null; street?: string | null;
number?: string | null;
complement?: string | null;
neighborhood?: string | null;
city?: string | null;
state?: string | null;
notes?: string | null;
}; };
// ===== MÉDICOS ===== // ===== MÉDICOS =====
export type FormacaoAcademica = { export type FormacaoAcademica = {
instituicao: string; instituicao: string;
@ -66,9 +78,10 @@ export type DadosBancarios = {
tipo_conta: string; tipo_conta: string;
}; };
// ===== MÉDICOS =====
export type Medico = { export type Medico = {
id: string; id: string;
nome?: string; full_name: string; // Altere 'nome' para 'full_name'
nome_social?: string | null; nome_social?: string | null;
cpf?: string; cpf?: string;
rg?: string | null; rg?: string | null;
@ -90,32 +103,51 @@ export type Medico = {
dados_bancarios?: DadosBancarios; dados_bancarios?: DadosBancarios;
agenda_horario?: string; agenda_horario?: string;
valor_consulta?: number | string; valor_consulta?: number | string;
active?: boolean;
cep?: string;
city?: string;
complement?: string;
neighborhood?: string;
number?: string;
phone2?: string;
state?: string;
street?: string;
created_at?: string;
created_by?: string;
updated_at?: string;
updated_by?: string;
user_id?: string;
}; };
// ===== MÉDICOS =====
// ...existing code...
export type MedicoInput = { export type MedicoInput = {
nome: string; user_id?: string | null;
nome_social?: string | null;
cpf?: string | null;
rg?: string | null;
sexo?: string | null;
data_nascimento?: string | null;
telefone?: string | null;
celular?: string | null;
contato_emergencia?: string | null;
email?: string | null;
crm: string; crm: string;
estado_crm?: string | null; crm_uf: string;
rqe?: string | null; specialty: string;
formacao_academica?: FormacaoAcademica[]; full_name: string;
curriculo_url?: string | null; cpf: string;
especialidade: string; email: string;
observacoes?: string | null; phone_mobile: string;
tipo_vinculo?: string | null; phone2?: string | null;
dados_bancarios?: DadosBancarios | null; cep: string;
agenda_horario?: string | null; street: string;
valor_consulta?: number | string | null; number: string;
complement?: string;
neighborhood?: string;
city: string;
state: string;
birth_date: string | null;
rg?: string | null;
active?: boolean;
created_by?: string | null;
updated_by?: string | null;
}; };
// ===== CONFIG ===== // ===== CONFIG =====
const API_BASE = const API_BASE =
process.env.NEXT_PUBLIC_API_BASE ?? "https://yuanqfswhberkoevtmfr.supabase.co"; process.env.NEXT_PUBLIC_API_BASE ?? "https://yuanqfswhberkoevtmfr.supabase.co";
@ -154,16 +186,21 @@ async function parse<T>(res: Response): Promise<T> {
let json: any = null; let json: any = null;
try { try {
json = await res.json(); json = await res.json();
} catch {} } catch (err) {
console.error("Erro ao parsear a resposta:", err);
}
if (!res.ok) { if (!res.ok) {
console.error("[API ERROR]", res.url, res.status, json); console.error("[API ERROR]", res.url, res.status, json);
const code = (json && (json.error?.code || json.code)) ?? res.status; const code = (json && (json.error?.code || json.code)) ?? res.status;
const msg = (json && (json.error?.message || json.message)) ?? res.statusText; const msg = (json && (json.error?.message || json.message)) ?? res.statusText;
throw new Error(`${code}: ${msg}`); throw new Error(`${code}: ${msg}`);
} }
return (json?.data ?? json) as T; return (json?.data ?? json) as T;
} }
// Helper de paginação (Range/Range-Unit) // Helper de paginação (Range/Range-Unit)
function rangeHeaders(page?: number, limit?: number): Record<string, string> { function rangeHeaders(page?: number, limit?: number): Record<string, string> {
if (!page || !limit) return {}; if (!page || !limit) return {};
@ -192,6 +229,68 @@ export async function listarPacientes(params?: {
return await parse<Paciente[]>(res); return await parse<Paciente[]>(res);
} }
// Nova função para busca avançada de pacientes
export async function buscarPacientes(termo: string): Promise<Paciente[]> {
if (!termo || termo.trim().length < 2) {
return [];
}
const searchTerm = termo.toLowerCase().trim();
const digitsOnly = searchTerm.replace(/\D/g, '');
// Monta queries para buscar em múltiplos campos
const queries = [];
// Busca por ID se parece com UUID
if (searchTerm.includes('-') && searchTerm.length > 10) {
queries.push(`id=eq.${searchTerm}`);
}
// Busca por CPF (com e sem formatação)
if (digitsOnly.length >= 11) {
queries.push(`cpf=eq.${digitsOnly}`);
} else if (digitsOnly.length >= 3) {
queries.push(`cpf=ilike.*${digitsOnly}*`);
}
// Busca por nome (usando ilike para busca case-insensitive)
if (searchTerm.length >= 2) {
queries.push(`full_name=ilike.*${searchTerm}*`);
queries.push(`social_name=ilike.*${searchTerm}*`);
}
// Busca por email se contém @
if (searchTerm.includes('@')) {
queries.push(`email=ilike.*${searchTerm}*`);
}
const results: Paciente[] = [];
const seenIds = new Set<string>();
// Executa as buscas e combina resultados únicos
for (const query of queries) {
try {
const url = `${REST}/patients?${query}&limit=10`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
const arr = await parse<Paciente[]>(res);
if (arr?.length > 0) {
for (const paciente of arr) {
if (!seenIds.has(paciente.id)) {
seenIds.add(paciente.id);
results.push(paciente);
}
}
}
} catch (error) {
console.warn(`Erro na busca com query: ${query}`, error);
}
}
return results.slice(0, 20); // Limita a 20 resultados
}
export async function buscarPacientePorId(id: string | number): Promise<Paciente> { export async function buscarPacientePorId(id: string | number): Promise<Paciente> {
const url = `${REST}/patients?id=eq.${id}`; const url = `${REST}/patients?id=eq.${id}`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const res = await fetch(url, { method: "GET", headers: baseHeaders() });
@ -262,34 +361,189 @@ export async function listarMedicos(params?: {
return await parse<Medico[]>(res); return await parse<Medico[]>(res);
} }
// Nova função para busca avançada de médicos
export async function buscarMedicos(termo: string): Promise<Medico[]> {
if (!termo || termo.trim().length < 2) {
return [];
}
const searchTerm = termo.toLowerCase().trim();
const digitsOnly = searchTerm.replace(/\D/g, '');
// Monta queries para buscar em múltiplos campos
const queries = [];
// Busca por ID se parece com UUID
if (searchTerm.includes('-') && searchTerm.length > 10) {
queries.push(`id=eq.${searchTerm}`);
}
// Busca por CRM (com e sem formatação)
if (digitsOnly.length >= 3) {
queries.push(`crm=ilike.*${digitsOnly}*`);
}
// Busca por nome (usando ilike para busca case-insensitive)
if (searchTerm.length >= 2) {
queries.push(`full_name=ilike.*${searchTerm}*`);
queries.push(`nome_social=ilike.*${searchTerm}*`);
}
// Busca por email se contém @
if (searchTerm.includes('@')) {
queries.push(`email=ilike.*${searchTerm}*`);
}
// Busca por especialidade
if (searchTerm.length >= 2) {
queries.push(`specialty=ilike.*${searchTerm}*`);
}
const results: Medico[] = [];
const seenIds = new Set<string>();
// Executa as buscas e combina resultados únicos
for (const query of queries) {
try {
const url = `${REST}/doctors?${query}&limit=10`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
const arr = await parse<Medico[]>(res);
if (arr?.length > 0) {
for (const medico of arr) {
if (!seenIds.has(medico.id)) {
seenIds.add(medico.id);
results.push(medico);
}
}
}
} catch (error) {
console.warn(`Erro na busca com query: ${query}`, error);
}
}
return results.slice(0, 20); // Limita a 20 resultados
}
export async function buscarMedicoPorId(id: string | number): Promise<Medico> { export async function buscarMedicoPorId(id: string | number): Promise<Medico> {
// Primeiro tenta buscar no Supabase (dados reais)
try {
const url = `${REST}/doctors?id=eq.${id}`; const url = `${REST}/doctors?id=eq.${id}`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const res = await fetch(url, { method: "GET", headers: baseHeaders() });
const arr = await parse<Medico[]>(res); const arr = await parse<Medico[]>(res);
if (!arr?.length) throw new Error("404: Médico não encontrado"); if (arr && arr.length > 0) {
console.log('✅ Médico encontrado no Supabase:', arr[0]);
console.log('🔍 Campo especialidade no médico:', {
especialidade: arr[0].especialidade,
specialty: (arr[0] as any).specialty,
hasEspecialidade: !!arr[0].especialidade,
hasSpecialty: !!((arr[0] as any).specialty)
});
return arr[0]; return arr[0];
} }
} catch (error) {
console.warn('⚠️ Erro ao buscar no Supabase, tentando mock API:', error);
}
// Se não encontrar no Supabase, tenta o mock API
try {
const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctors/${id}`;
const res = await fetch(url, {
method: "GET",
headers: {
"Accept": "application/json"
}
});
if (!res.ok) {
if (res.status === 404) {
throw new Error("404: Médico não encontrado");
}
throw new Error(`Erro ao buscar médico: ${res.status} ${res.statusText}`);
}
const medico = await res.json();
console.log('✅ Médico encontrado no Mock API:', medico);
return medico as Medico;
} catch (error) {
console.error('❌ Erro ao buscar médico em ambas as APIs:', error);
throw new Error("404: Médico não encontrado");
}
}
// Dentro de lib/api.ts
export async function criarMedico(input: MedicoInput): Promise<Medico> { export async function criarMedico(input: MedicoInput): Promise<Medico> {
const url = `${REST}/doctors`; console.log("Enviando os dados para a API:", input); // Log para depuração
const url = `${REST}/doctors`; // Endpoint de médicos
const res = await fetch(url, { const res = await fetch(url, {
method: "POST", method: "POST",
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"), headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
body: JSON.stringify(input), body: JSON.stringify(input), // Enviando os dados padronizados
}); });
const arr = await parse<Medico[] | Medico>(res);
return Array.isArray(arr) ? arr[0] : (arr as Medico); const arr = await parse<Medico[] | Medico>(res); // Resposta da API
return Array.isArray(arr) ? arr[0] : (arr as Medico); // Retorno do médico
} }
export async function atualizarMedico(id: string | number, input: MedicoInput): Promise<Medico> { export async function atualizarMedico(id: string | number, input: MedicoInput): Promise<Medico> {
console.log(`🔄 Tentando atualizar médico ID: ${id}`);
console.log(`📤 Payload original:`, input);
// Criar um payload limpo apenas com campos básicos que sabemos que existem
const cleanPayload = {
full_name: input.full_name,
crm: input.crm,
specialty: input.specialty,
email: input.email,
phone_mobile: input.phone_mobile,
cpf: input.cpf,
cep: input.cep,
street: input.street,
number: input.number,
city: input.city,
state: input.state,
active: input.active ?? true
};
console.log(`📤 Payload limpo:`, cleanPayload);
// Atualizar apenas no Supabase (dados reais)
try {
const url = `${REST}/doctors?id=eq.${id}`; const url = `${REST}/doctors?id=eq.${id}`;
console.log(`🌐 URL de atualização: ${url}`);
const res = await fetch(url, { const res = await fetch(url, {
method: "PATCH", method: "PATCH",
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"), headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
body: JSON.stringify(input), body: JSON.stringify(cleanPayload),
}); });
console.log(`📡 Resposta do servidor: ${res.status} ${res.statusText}`);
if (res.ok) {
const arr = await parse<Medico[] | Medico>(res); const arr = await parse<Medico[] | Medico>(res);
return Array.isArray(arr) ? arr[0] : (arr as Medico); const result = Array.isArray(arr) ? arr[0] : (arr as Medico);
console.log('✅ Médico atualizado no Supabase:', result);
return result;
} else {
// Vamos tentar ver o erro detalhado
const errorText = await res.text();
console.error(`❌ Erro detalhado do Supabase:`, {
status: res.status,
statusText: res.statusText,
response: errorText,
headers: Object.fromEntries(res.headers.entries())
});
throw new Error(`Supabase error: ${res.status} ${res.statusText} - ${errorText}`);
}
} catch (error) {
console.error('❌ Erro ao atualizar médico:', error);
throw error;
}
} }
export async function excluirMedico(id: string | number): Promise<void> { export async function excluirMedico(id: string | number): Promise<void> {

View File

@ -4086,6 +4086,53 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
"es-define-property": "^1.0.0",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001739", "version": "1.0.30001739",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
@ -4578,10 +4625,51 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
=======
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
>>>>>>> origin/develop
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -4727,6 +4815,8 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.24.0", "version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
@ -4796,6 +4886,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -4814,6 +4905,8 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/es-iterator-helpers": { "node_modules/es-iterator-helpers": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
@ -4842,6 +4935,7 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
>>>>>>> origin/develop
"node_modules/es-object-atoms": { "node_modules/es-object-atoms": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@ -4854,6 +4948,8 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/es-set-tostringtag": { "node_modules/es-set-tostringtag": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
@ -4901,6 +4997,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/es-toolkit": { "node_modules/es-toolkit": {
"version": "1.39.10", "version": "1.39.10",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
@ -5767,6 +5864,8 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/function.prototype.name": { "node_modules/function.prototype.name": {
"version": "1.1.8", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
@ -5788,6 +5887,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/functions-have-names": { "node_modules/functions-have-names": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
@ -5806,6 +5906,8 @@
"next": ">=13.2.0" "next": ">=13.2.0"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/generator-function": { "node_modules/generator-function": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@ -5816,6 +5918,7 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
>>>>>>> origin/develop
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@ -5862,6 +5965,8 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/get-symbol-description": { "node_modules/get-symbol-description": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@ -5936,6 +6041,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -5955,6 +6061,8 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
<<<<<<< HEAD
=======
"node_modules/graphemer": { "node_modules/graphemer": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@ -5985,6 +6093,7 @@
"node": ">=8" "node": ">=8"
} }
}, },
>>>>>>> origin/develop
"node_modules/has-property-descriptors": { "node_modules/has-property-descriptors": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
@ -5997,6 +6106,8 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/has-proto": { "node_modules/has-proto": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
@ -6013,6 +6124,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/has-symbols": { "node_modules/has-symbols": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@ -6182,6 +6294,8 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@ -6326,6 +6440,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/is-date-object": { "node_modules/is-date-object": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
@ -6342,6 +6457,8 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/is-extglob": { "node_modules/is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -6454,6 +6571,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/is-regex": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@ -6472,6 +6590,8 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/is-set": { "node_modules/is-set": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
@ -6630,6 +6750,7 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
>>>>>>> origin/develop
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
@ -7091,6 +7212,8 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -7141,6 +7264,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/minipass": { "node_modules/minipass": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@ -7342,6 +7466,8 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@ -7355,6 +7481,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/object-is": { "node_modules/object-is": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
@ -7380,6 +7507,8 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/object.assign": { "node_modules/object.assign": {
"version": "4.1.7", "version": "4.1.7",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
@ -7538,6 +7667,7 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
>>>>>>> origin/develop
"node_modules/pako": { "node_modules/pako": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
@ -8070,6 +8200,8 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
<<<<<<< HEAD
=======
"node_modules/regexp-tree": { "node_modules/regexp-tree": {
"version": "0.1.27", "version": "0.1.27",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
@ -8080,6 +8212,7 @@
"regexp-tree": "bin/regexp-tree" "regexp-tree": "bin/regexp-tree"
} }
}, },
>>>>>>> origin/develop
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@ -8100,6 +8233,8 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/regjsparser": { "node_modules/regjsparser": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
@ -8126,6 +8261,7 @@
"node": ">=6" "node": ">=6"
} }
}, },
>>>>>>> origin/develop
"node_modules/reselect": { "node_modules/reselect": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@ -8333,6 +8469,8 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
<<<<<<< HEAD
=======
"node_modules/set-proto": { "node_modules/set-proto": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
@ -8490,6 +8628,7 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
>>>>>>> origin/develop
"node_modules/signature_pad": { "node_modules/signature_pad": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-2.3.2.tgz", "resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-2.3.2.tgz",