Merge pull request 'adicionando-atualização-medicos' (#28) from feature/ajustes-form-medico into develop
Reviewed-on: #28
This commit is contained in:
commit
e85fbdeb15
@ -12,12 +12,12 @@ import { Badge } from "@/components/ui/badge";
|
||||
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 {
|
||||
return {
|
||||
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,
|
||||
cpf: m.cpf ?? "",
|
||||
rg: m.rg ?? m.document_number ?? null,
|
||||
@ -39,6 +39,20 @@ function normalizeMedico(m: any): Medico {
|
||||
dados_bancarios: m.dados_bancarios ?? null,
|
||||
agenda_horario: m.agenda_horario ?? 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 [editingId, setEditingId] = useState<string | 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() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const list = await listarMedicos({ limit: 50 });
|
||||
setDoctors((list ?? []).map(normalizeMedico));
|
||||
const normalized = (list ?? []).map(normalizeMedico);
|
||||
console.log('🏥 Médicos carregados:', normalized);
|
||||
setDoctors(normalized);
|
||||
|
||||
} finally {
|
||||
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(() => {
|
||||
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;
|
||||
const q = search.toLowerCase();
|
||||
return doctors.filter((d) => {
|
||||
const byName = (d.nome || "").toLowerCase().includes(q);
|
||||
const byCrm = (d.crm || "").toLowerCase().includes(q);
|
||||
|
||||
const q = search.toLowerCase().trim();
|
||||
const qDigits = q.replace(/\D/g, "");
|
||||
|
||||
// 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);
|
||||
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() {
|
||||
setEditingId(null);
|
||||
@ -139,7 +298,7 @@ setDoctors((list ?? []).map(normalizeMedico));
|
||||
<DoctorRegistrationForm
|
||||
inline
|
||||
mode={editingId ? "edit" : "create"}
|
||||
doctorId={editingId ? Number(editingId) : null}
|
||||
doctorId={editingId}
|
||||
onSaved={handleSaved}
|
||||
onClose={() => setShowForm(false)}
|
||||
/>
|
||||
@ -156,14 +315,36 @@ setDoctors((list ?? []).map(normalizeMedico));
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
className="pl-8 w-80"
|
||||
placeholder="Buscar por nome, CRM ou especialidade…"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
className="pl-8 w-80"
|
||||
placeholder="Digite para buscar por ID, nome, CRM ou especialidade…"
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
/>
|
||||
</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}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
@ -190,10 +371,10 @@ setDoctors((list ?? []).map(normalizeMedico));
|
||||
Carregando…
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : filtered.length > 0 ? (
|
||||
filtered.map((doctor) => (
|
||||
) : displayedDoctors.length > 0 ? (
|
||||
displayedDoctors.map((doctor) => (
|
||||
<TableRow key={doctor.id}>
|
||||
<TableCell className="font-medium">{doctor.nome}</TableCell>
|
||||
<TableCell className="font-medium">{doctor.full_name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{doctor.especialidade}</Badge>
|
||||
</TableCell>
|
||||
@ -247,13 +428,13 @@ setDoctors((list ?? []).map(normalizeMedico));
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detalhes do Médico</DialogTitle>
|
||||
<DialogDescription>
|
||||
Informações detalhadas de {viewingDoctor?.nome}.
|
||||
Informações detalhadas de {viewingDoctor?.full_name}.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<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 className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">Especialidade</Label>
|
||||
@ -282,7 +463,7 @@ setDoctors((list ?? []).map(normalizeMedico));
|
||||
)}
|
||||
|
||||
<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>
|
||||
);
|
||||
|
||||
@ -10,34 +10,29 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
|
||||
import { Label } from "@/components/ui/label";
|
||||
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";
|
||||
|
||||
|
||||
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 {
|
||||
id: String(p.id ?? p.uuid ?? p.paciente_id ?? ""),
|
||||
nome: p.full_name ?? "", // 👈 troca nome → full_name
|
||||
nome_social: p.social_name ?? null, // 👈 Supabase usa social_name
|
||||
full_name: p.full_name ?? p.name ?? p.nome ?? "",
|
||||
social_name: p.social_name ?? p.nome_social ?? null,
|
||||
cpf: p.cpf ?? "",
|
||||
rg: p.rg ?? p.document_number ?? null, // 👈 às vezes vem como document_number
|
||||
sexo: p.sexo ?? p.sex ?? null, // 👈 Supabase usa sex
|
||||
data_nascimento: p.data_nascimento ?? p.birth_date ?? null,
|
||||
telefone: p.telefone ?? p.phone_mobile ?? "",
|
||||
rg: p.rg ?? p.document_number ?? null,
|
||||
sex: p.sex ?? p.sexo ?? null,
|
||||
birth_date: p.birth_date ?? p.data_nascimento ?? null,
|
||||
phone_mobile: p.phone_mobile ?? p.telefone ?? "",
|
||||
email: p.email ?? "",
|
||||
endereco,
|
||||
observacoes: p.observacoes ?? p.notes ?? null,
|
||||
foto_url: p.foto_url ?? null,
|
||||
cep: p.cep ?? "",
|
||||
street: p.street ?? p.logradouro ?? "",
|
||||
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 {
|
||||
setLoading(true);
|
||||
const data = await listarPacientes({ page: 1, limit: 20 });
|
||||
setPatients((data ?? []).map(normalizePaciente));
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
setPatients(data.map(normalizePaciente));
|
||||
} else {
|
||||
setPatients([]);
|
||||
}
|
||||
setError(null);
|
||||
} catch (e: any) {
|
||||
setPatients([]);
|
||||
@ -72,13 +72,23 @@ export default function PacientesPage() {
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!search.trim()) return patients;
|
||||
const q = search.toLowerCase();
|
||||
const q = search.toLowerCase().trim();
|
||||
const qDigits = q.replace(/\D/g, "");
|
||||
|
||||
return patients.filter((p) => {
|
||||
const byName = (p.nome || "").toLowerCase().includes(q);
|
||||
const byCPF = (p.cpf || "").replace(/\D/g, "").includes(qDigits);
|
||||
const byId = String(p.id || "").includes(qDigits);
|
||||
return byName || byCPF || byId;
|
||||
// Busca por nome
|
||||
const byName = (p.full_name || "").toLowerCase().includes(q);
|
||||
|
||||
// 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]);
|
||||
|
||||
@ -122,25 +132,33 @@ export default function PacientesPage() {
|
||||
const q = search.trim();
|
||||
if (!q) return loadAll();
|
||||
|
||||
|
||||
if (/^\d+$/.test(q)) {
|
||||
try {
|
||||
setLoading(true);
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Se parece com ID (UUID), busca diretamente
|
||||
if (q.includes('-') && q.length > 10) {
|
||||
const one = await buscarPacientePorId(q);
|
||||
setPatients(one ? [normalizePaciente(one)] : []);
|
||||
setError(one ? null : "Paciente não encontrado.");
|
||||
} catch (e: any) {
|
||||
setPatients([]);
|
||||
setError(e?.message || "Paciente não encontrado.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Limpa o campo de busca para que o filtro não interfira
|
||||
setSearch("");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await loadAll();
|
||||
setTimeout(() => setSearch(q), 0);
|
||||
// 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("");
|
||||
|
||||
} catch (e: any) {
|
||||
setPatients([]);
|
||||
setError(e?.message || "Erro na busca.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <p>Carregando pacientes...</p>;
|
||||
@ -159,7 +177,7 @@ export default function PacientesPage() {
|
||||
<PatientRegistrationForm
|
||||
inline
|
||||
mode={editingId ? "edit" : "create"}
|
||||
patientId={editingId ? Number(editingId) : null}
|
||||
patientId={editingId}
|
||||
onSaved={handleSaved}
|
||||
onClose={() => setShowForm(false)}
|
||||
/>
|
||||
@ -210,11 +228,11 @@ export default function PacientesPage() {
|
||||
{filtered.length > 0 ? (
|
||||
filtered.map((p) => (
|
||||
<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.telefone || "-"}</TableCell>
|
||||
<TableCell>{p.endereco?.cidade || "-"}</TableCell>
|
||||
<TableCell>{p.endereco?.estado || "-"}</TableCell>
|
||||
<TableCell>{p.phone_mobile || "-"}</TableCell>
|
||||
<TableCell>{p.city || "-"}</TableCell>
|
||||
<TableCell>{p.state || "-"}</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@ -258,13 +276,13 @@ export default function PacientesPage() {
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detalhes do Paciente</DialogTitle>
|
||||
<DialogDescription>
|
||||
Informações detalhadas de {viewingPatient.nome}.
|
||||
Informações detalhadas de {viewingPatient.full_name}.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<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 className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">CPF</Label>
|
||||
@ -272,17 +290,17 @@ export default function PacientesPage() {
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<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 className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">Endereço</Label>
|
||||
<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>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<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>
|
||||
<DialogFooter>
|
||||
|
||||
@ -7,6 +7,7 @@ import "react-quill/dist/quill.snow.css";
|
||||
import Link from "next/link";
|
||||
import ProtectedRoute from "@/components/ProtectedRoute";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { buscarPacientes } from "@/lib/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@ -566,53 +567,189 @@ const ProfissionalPage = () => {
|
||||
};
|
||||
|
||||
|
||||
const renderPacientesSection = () => (
|
||||
<div className="bg-white shadow-md rounded-lg p-6">
|
||||
<h2 className="text-2xl font-bold mb-4">Gerenciamento de Pacientes</h2>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Paciente</TableHead>
|
||||
<TableHead>CPF</TableHead>
|
||||
<TableHead>Idade</TableHead>
|
||||
<TableHead>Status do laudo</TableHead>
|
||||
<TableHead>Ações</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pacientes.map((paciente) => (
|
||||
<TableRow key={paciente.cpf}>
|
||||
<TableCell className="font-medium">{paciente.nome}</TableCell>
|
||||
<TableCell>{paciente.cpf}</TableCell>
|
||||
<TableCell>{paciente.idade}</TableCell>
|
||||
<TableCell>{paciente.statusLaudo}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-primary text-primary hover:bg-primary hover:text-white cursor-pointer"
|
||||
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">
|
||||
<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 className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-1 bg-gray-900 text-white text-xs rounded-md opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none whitespace-nowrap z-50">
|
||||
Ver informações do paciente
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tabela de pacientes padrão */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-3">Pacientes Recentes</h3>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Paciente</TableHead>
|
||||
<TableHead>CPF</TableHead>
|
||||
<TableHead>Idade</TableHead>
|
||||
<TableHead>Status do laudo</TableHead>
|
||||
<TableHead>Ações</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pacientes.map((paciente) => (
|
||||
<TableRow key={paciente.cpf}>
|
||||
<TableCell className="font-medium">{paciente.nome}</TableCell>
|
||||
<TableCell>{paciente.cpf}</TableCell>
|
||||
<TableCell>{paciente.idade}</TableCell>
|
||||
<TableCell>{paciente.statusLaudo}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-primary text-primary hover:bg-primary hover:text-white cursor-pointer"
|
||||
onClick={() => {
|
||||
handleAbrirProntuario(paciente);
|
||||
setActiveSection('prontuario');
|
||||
}}
|
||||
>
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-1 bg-gray-900 text-white text-xs rounded-md opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none whitespace-nowrap z-50">
|
||||
Ver informações do paciente
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const renderProntuarioSection = () => (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { Bell, Search, ChevronDown } from "lucide-react"
|
||||
import { Bell, ChevronDown } from "lucide-react"
|
||||
import { useAuth } from "@/hooks/useAuth"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
@ -40,11 +40,6 @@ export function PagesHeader({ title = "", subtitle = "" }: { title?: string, sub
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<Bell className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { buscarPacientePorId } from "@/lib/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@ -21,8 +22,10 @@ import {
|
||||
listarAnexosMedico,
|
||||
adicionarAnexoMedico,
|
||||
removerAnexoMedico,
|
||||
MedicoInput,
|
||||
MedicoInput, // 👈 importado do lib/api
|
||||
Medico, // 👈 adicionado import do tipo Medico
|
||||
} from "@/lib/api";
|
||||
;
|
||||
|
||||
import { buscarCepAPI } from "@/lib/api";
|
||||
|
||||
@ -39,39 +42,16 @@ type DadosBancarios = {
|
||||
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";
|
||||
|
||||
export interface DoctorRegistrationFormProps {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
doctorId?: number | null;
|
||||
doctorId?: string | number | null;
|
||||
inline?: boolean;
|
||||
mode?: Mode;
|
||||
onSaved?: (medico: Medico) => void;
|
||||
@ -80,7 +60,7 @@ export interface DoctorRegistrationFormProps {
|
||||
|
||||
type FormData = {
|
||||
photo: File | null;
|
||||
nome: string;
|
||||
full_name: string; // Substitua 'nome' por 'full_name'
|
||||
nome_social: string;
|
||||
crm: string;
|
||||
estado_crm: string;
|
||||
@ -107,14 +87,13 @@ type FormData = {
|
||||
anexos: File[];
|
||||
tipo_vinculo: string;
|
||||
dados_bancarios: DadosBancarios;
|
||||
|
||||
agenda_horario: string;
|
||||
valor_consulta: string;
|
||||
};
|
||||
|
||||
const initial: FormData = {
|
||||
photo: null,
|
||||
nome: "",
|
||||
full_name: "",
|
||||
nome_social: "",
|
||||
crm: "",
|
||||
estado_crm: "",
|
||||
@ -128,7 +107,7 @@ const initial: FormData = {
|
||||
data_nascimento: "",
|
||||
email: "",
|
||||
telefone: "",
|
||||
celular: "",
|
||||
celular: "", // Aqui, 'celular' pode ser 'phone_mobile'
|
||||
contato_emergencia: "",
|
||||
cep: "",
|
||||
logradouro: "",
|
||||
@ -152,6 +131,7 @@ const initial: FormData = {
|
||||
|
||||
|
||||
|
||||
|
||||
export function DoctorRegistrationForm({
|
||||
open = true,
|
||||
onOpenChange,
|
||||
@ -175,46 +155,78 @@ export function DoctorRegistrationForm({
|
||||
let alive = true;
|
||||
async function load() {
|
||||
if (mode === "edit" && doctorId) {
|
||||
const medico = await buscarMedicoPorId(doctorId);
|
||||
if (!alive) return;
|
||||
setForm({
|
||||
photo: null,
|
||||
nome: medico.nome ?? "",
|
||||
nome_social: medico.nome_social ?? "",
|
||||
crm: medico.crm ?? "",
|
||||
estado_crm: medico.estado_crm ?? "",
|
||||
rqe: medico.rqe ?? "",
|
||||
formacao_academica: medico.formacao_academica ?? [],
|
||||
curriculo: null,
|
||||
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) : "",
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
const list = await listarAnexosMedico(doctorId);
|
||||
setServerAnexos(list ?? []);
|
||||
} catch {}
|
||||
console.log("[DoctorForm] Carregando médico ID:", doctorId);
|
||||
const medico = await buscarMedicoPorId(String(doctorId));
|
||||
console.log("[DoctorForm] Dados recebidos do API:", medico);
|
||||
console.log("[DoctorForm] Campos principais:", {
|
||||
full_name: medico.full_name,
|
||||
crm: medico.crm,
|
||||
especialidade: medico.especialidade,
|
||||
specialty: (medico as any).specialty,
|
||||
cpf: medico.cpf,
|
||||
email: medico.email
|
||||
});
|
||||
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 {
|
||||
const list = await listarAnexosMedico(String(doctorId));
|
||||
setServerAnexos(list ?? []);
|
||||
} catch (err) {
|
||||
console.error("[DoctorForm] Erro ao carregar anexos:", err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[DoctorForm] Erro ao carregar médico:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
load();
|
||||
@ -222,10 +234,11 @@ export function DoctorRegistrationForm({
|
||||
}, [mode, doctorId]);
|
||||
|
||||
|
||||
function setField<T extends keyof FormData>(k: T, v: FormData[T]) {
|
||||
setForm((s) => ({ ...s, [k]: v }));
|
||||
if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" }));
|
||||
}
|
||||
function setField<T extends keyof FormData>(k: T, v: FormData[T]) {
|
||||
setForm((s) => ({ ...s, [k]: v }));
|
||||
if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
function addFormacao() {
|
||||
@ -299,76 +312,92 @@ export function DoctorRegistrationForm({
|
||||
}
|
||||
|
||||
|
||||
function validateLocal(): boolean {
|
||||
const e: Record<string, string> = {};
|
||||
if (!form.nome.trim()) e.nome = "Nome é obrigatório";
|
||||
if (!form.cpf.trim()) e.cpf = "CPF é obrigatório";
|
||||
if (!form.crm.trim()) e.crm = "CRM é obrigatório";
|
||||
if (!form.especialidade.trim()) e.especialidade = "Especialidade é obrigatória";
|
||||
setErrors(e);
|
||||
return Object.keys(e).length === 0;
|
||||
}
|
||||
function validateLocal(): boolean {
|
||||
const e: Record<string, string> = {};
|
||||
|
||||
async function handleSubmit(ev: React.FormEvent) {
|
||||
if (!form.full_name.trim()) e.full_name = "Nome é obrigatório";
|
||||
if (!form.cpf.trim()) e.cpf = "CPF é obrigatório";
|
||||
if (!form.crm.trim()) e.crm = "CRM é obrigatório";
|
||||
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);
|
||||
return Object.keys(e).length === 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function handleSubmit(ev: React.FormEvent) {
|
||||
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);
|
||||
setErrors((e) => ({ ...e, submit: "" }));
|
||||
|
||||
try {
|
||||
const payload: MedicoInput = {
|
||||
nome: form.nome,
|
||||
nome_social: form.nome_social || null,
|
||||
cpf: form.cpf || null,
|
||||
rg: form.rg || null,
|
||||
sexo: form.sexo || null,
|
||||
data_nascimento: form.data_nascimento || null,
|
||||
telefone: form.telefone || 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,
|
||||
};
|
||||
const payload: MedicoInput = {
|
||||
user_id: null,
|
||||
crm: form.crm || "",
|
||||
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,
|
||||
active: true,
|
||||
created_by: null,
|
||||
updated_by: 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"
|
||||
? await criarMedico(payload)
|
||||
: await atualizarMedico(doctorId as number, payload);
|
||||
: await atualizarMedico(String(doctorId), payload);
|
||||
|
||||
const medicoId = saved.id;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("✅ Médico salvo com sucesso:", saved);
|
||||
|
||||
onSaved?.(saved);
|
||||
if (inline) onClose?.();
|
||||
else onOpenChange?.(false);
|
||||
setSubmitting(false);
|
||||
} 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" }));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
@ -376,6 +405,10 @@ export function DoctorRegistrationForm({
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const f = e.target.files?.[0];
|
||||
if (!f) return;
|
||||
@ -449,8 +482,10 @@ export function DoctorRegistrationForm({
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Nome *</Label>
|
||||
<Input value={form.nome} onChange={(e) => setField("nome", e.target.value)} className={errors.nome ? "border-destructive" : ""} />
|
||||
{errors.nome && <p className="text-sm text-destructive">{errors.nome}</p>}
|
||||
<Input value={form.full_name} onChange={(e) => setField("full_name", e.target.value)} />
|
||||
|
||||
|
||||
{errors.full_name && <p className="text-sm text-destructive">{errors.full_name}</p>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Nome Social</Label>
|
||||
@ -471,16 +506,21 @@ export function DoctorRegistrationForm({
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Especialidade *</Label>
|
||||
<Input value={form.especialidade} onChange={(e) => setField("especialidade", e.target.value)} className={errors.especialidade ? "border-destructive" : ""} />
|
||||
{errors.especialidade && <p className="text-sm text-destructive">{errors.especialidade}</p>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>RQE</Label>
|
||||
<Input value={form.rqe} onChange={(e) => setField("rqe", e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Especialidade *</Label>
|
||||
<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>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>RQE</Label>
|
||||
<Input value={form.rqe} onChange={(e) => setField("rqe", e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Currículo</Label>
|
||||
@ -629,14 +669,25 @@ export function DoctorRegistrationForm({
|
||||
<Label>E-mail</Label>
|
||||
<Input value={form.email} onChange={(e) => setField("email", e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Telefone</Label>
|
||||
<Input
|
||||
value={form.telefone}
|
||||
onChange={(e) => setField("telefone", formatPhone(e.target.value))}
|
||||
placeholder="(XX) XXXXX-XXXX"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Telefone</Label>
|
||||
<Input
|
||||
value={form.telefone}
|
||||
onChange={(e) => setField("telefone", formatPhone(e.target.value))}
|
||||
placeholder="(XX) XXXXX-XXXX"
|
||||
/>
|
||||
</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 className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
@ -703,11 +754,14 @@ export function DoctorRegistrationForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Agenda/Horário</Label>
|
||||
<Textarea
|
||||
value={form.agenda_horario}
|
||||
onChange={(e) => setField("agenda_horario", e.target.value)}
|
||||
placeholder="Descreva os dias e horários de atendimento"
|
||||
/>
|
||||
// Dentro do form, apenas exiba o campo se precisar dele visualmente, mas não envie
|
||||
<textarea
|
||||
value={form.agenda_horario}
|
||||
onChange={(e) => setField("agenda_horario", e.target.value)}
|
||||
placeholder="Descreva os dias e horários de atendimento"
|
||||
disabled={true} // Torne o campo apenas visual, sem enviar
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
|
||||
@ -37,7 +37,7 @@ type Mode = "create" | "edit";
|
||||
export interface PatientRegistrationFormProps {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
patientId?: number | null;
|
||||
patientId?: string | number | null;
|
||||
inline?: boolean;
|
||||
mode?: Mode;
|
||||
onSaved?: (paciente: Paciente) => void;
|
||||
@ -51,7 +51,7 @@ type FormData = {
|
||||
cpf: string;
|
||||
rg: string;
|
||||
sexo: string;
|
||||
data_nascimento: string;
|
||||
birth_date: string; // 👈 corrigido
|
||||
email: string;
|
||||
telefone: string;
|
||||
cep: string;
|
||||
@ -72,7 +72,7 @@ const initial: FormData = {
|
||||
cpf: "",
|
||||
rg: "",
|
||||
sexo: "",
|
||||
data_nascimento: "",
|
||||
birth_date: "", // 👈 corrigido
|
||||
email: "",
|
||||
telefone: "",
|
||||
cep: "",
|
||||
@ -86,6 +86,8 @@ const initial: FormData = {
|
||||
anexos: [],
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function PatientRegistrationForm({
|
||||
open = true,
|
||||
onOpenChange,
|
||||
@ -110,30 +112,33 @@ export function PatientRegistrationForm({
|
||||
async function load() {
|
||||
if (mode !== "edit" || patientId == null) return;
|
||||
try {
|
||||
console.log("[PatientForm] Carregando paciente ID:", patientId);
|
||||
const p = await buscarPacientePorId(String(patientId));
|
||||
console.log("[PatientForm] Dados recebidos:", p);
|
||||
setForm((s) => ({
|
||||
...s,
|
||||
nome: p.nome || "",
|
||||
nome_social: p.nome_social || "",
|
||||
cpf: p.cpf || "",
|
||||
rg: p.rg || "",
|
||||
sexo: p.sexo || "",
|
||||
data_nascimento: (p.data_nascimento as string) || "",
|
||||
telefone: p.telefone || "",
|
||||
email: p.email || "",
|
||||
cep: p.endereco?.cep || "",
|
||||
logradouro: p.endereco?.logradouro || "",
|
||||
numero: p.endereco?.numero || "",
|
||||
complemento: p.endereco?.complemento || "",
|
||||
bairro: p.endereco?.bairro || "",
|
||||
cidade: p.endereco?.cidade || "",
|
||||
estado: p.endereco?.estado || "",
|
||||
observacoes: p.observacoes || "",
|
||||
}));
|
||||
...s,
|
||||
nome: p.full_name || "", // 👈 trocar nome → full_name
|
||||
nome_social: p.social_name || "",
|
||||
cpf: p.cpf || "",
|
||||
rg: p.rg || "",
|
||||
sexo: p.sex || "",
|
||||
birth_date: p.birth_date || "", // 👈 trocar data_nascimento → birth_date
|
||||
telefone: p.phone_mobile || "",
|
||||
email: p.email || "",
|
||||
cep: p.cep || "",
|
||||
logradouro: p.street || "",
|
||||
numero: p.number || "",
|
||||
complemento: p.complement || "",
|
||||
bairro: p.neighborhood || "",
|
||||
cidade: p.city || "",
|
||||
estado: p.state || "",
|
||||
observacoes: p.notes || "",
|
||||
}));
|
||||
|
||||
const ax = await listarAnexos(String(patientId)).catch(() => []);
|
||||
setServerAnexos(Array.isArray(ax) ? ax : []);
|
||||
} catch {
|
||||
|
||||
} catch (err) {
|
||||
console.error("[PatientForm] Erro ao carregar paciente:", err);
|
||||
}
|
||||
}
|
||||
load();
|
||||
@ -186,27 +191,27 @@ export function PatientRegistrationForm({
|
||||
}
|
||||
|
||||
function toPayload(): PacienteInput {
|
||||
return {
|
||||
nome: form.nome,
|
||||
nome_social: form.nome_social || null,
|
||||
cpf: form.cpf,
|
||||
rg: form.rg || null,
|
||||
sexo: form.sexo || null,
|
||||
data_nascimento: form.data_nascimento || null,
|
||||
telefone: form.telefone || null,
|
||||
email: form.email || null,
|
||||
endereco: {
|
||||
cep: form.cep || undefined,
|
||||
logradouro: form.logradouro || undefined,
|
||||
numero: form.numero || undefined,
|
||||
complemento: form.complemento || undefined,
|
||||
bairro: form.bairro || undefined,
|
||||
cidade: form.cidade || undefined,
|
||||
estado: form.estado || undefined,
|
||||
},
|
||||
observacoes: form.observacoes || null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
full_name: form.nome, // 👈 troca 'nome' por 'full_name'
|
||||
social_name: form.nome_social || null,
|
||||
cpf: form.cpf,
|
||||
rg: form.rg || null,
|
||||
sex: form.sexo || null,
|
||||
birth_date: form.birth_date || null, // 👈 troca data_nascimento → birth_date
|
||||
phone_mobile: form.telefone || null,
|
||||
email: form.email || null,
|
||||
cep: form.cep || null,
|
||||
street: form.logradouro || null,
|
||||
number: form.numero || null,
|
||||
complement: form.complemento || null,
|
||||
neighborhood: form.bairro || null,
|
||||
city: form.cidade || null,
|
||||
state: form.estado || null,
|
||||
notes: form.observacoes || null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function handleSubmit(ev: React.FormEvent) {
|
||||
ev.preventDefault();
|
||||
@ -418,7 +423,8 @@ export function PatientRegistrationForm({
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<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>
|
||||
</CardContent>
|
||||
|
||||
@ -26,32 +26,44 @@ export type Endereco = {
|
||||
// ===== PACIENTES =====
|
||||
export type Paciente = {
|
||||
id: string;
|
||||
nome?: string;
|
||||
nome_social?: string | null;
|
||||
full_name: string;
|
||||
social_name?: string | null;
|
||||
cpf?: string;
|
||||
rg?: string | null;
|
||||
sexo?: string | null;
|
||||
data_nascimento?: string | null;
|
||||
telefone?: string;
|
||||
sex?: string | null;
|
||||
birth_date?: string | null;
|
||||
phone_mobile?: string;
|
||||
email?: string;
|
||||
endereco?: Endereco;
|
||||
observacoes?: string | null;
|
||||
foto_url?: string | null;
|
||||
cep?: string | null;
|
||||
street?: string | null;
|
||||
number?: string | null;
|
||||
complement?: string | null;
|
||||
neighborhood?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
notes?: string | null;
|
||||
};
|
||||
|
||||
export type PacienteInput = {
|
||||
nome: string;
|
||||
nome_social?: string | null;
|
||||
full_name: string;
|
||||
social_name?: string | null;
|
||||
cpf: string;
|
||||
rg?: string | null;
|
||||
sexo?: string | null;
|
||||
data_nascimento?: string | null;
|
||||
telefone?: string | null;
|
||||
sex?: string | null;
|
||||
birth_date?: string | null;
|
||||
phone_mobile?: string | null;
|
||||
email?: string | null;
|
||||
endereco?: Endereco;
|
||||
observacoes?: string | null;
|
||||
cep?: 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 =====
|
||||
export type FormacaoAcademica = {
|
||||
instituicao: string;
|
||||
@ -66,9 +78,10 @@ export type DadosBancarios = {
|
||||
tipo_conta: string;
|
||||
};
|
||||
|
||||
// ===== MÉDICOS =====
|
||||
export type Medico = {
|
||||
id: string;
|
||||
nome?: string;
|
||||
full_name: string; // Altere 'nome' para 'full_name'
|
||||
nome_social?: string | null;
|
||||
cpf?: string;
|
||||
rg?: string | null;
|
||||
@ -90,32 +103,51 @@ export type Medico = {
|
||||
dados_bancarios?: DadosBancarios;
|
||||
agenda_horario?: 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 = {
|
||||
nome: string;
|
||||
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;
|
||||
user_id?: string | null;
|
||||
crm: string;
|
||||
estado_crm?: string | null;
|
||||
rqe?: string | null;
|
||||
formacao_academica?: FormacaoAcademica[];
|
||||
curriculo_url?: string | null;
|
||||
especialidade: string;
|
||||
observacoes?: string | null;
|
||||
tipo_vinculo?: string | null;
|
||||
dados_bancarios?: DadosBancarios | null;
|
||||
agenda_horario?: string | null;
|
||||
valor_consulta?: number | string | null;
|
||||
crm_uf: string;
|
||||
specialty: string;
|
||||
full_name: string;
|
||||
cpf: string;
|
||||
email: string;
|
||||
phone_mobile: string;
|
||||
phone2?: string | null;
|
||||
cep: string;
|
||||
street: string;
|
||||
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 =====
|
||||
const API_BASE =
|
||||
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;
|
||||
try {
|
||||
json = await res.json();
|
||||
} catch {}
|
||||
} catch (err) {
|
||||
console.error("Erro ao parsear a resposta:", err);
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("[API ERROR]", res.url, res.status, json);
|
||||
const code = (json && (json.error?.code || json.code)) ?? res.status;
|
||||
const msg = (json && (json.error?.message || json.message)) ?? res.statusText;
|
||||
throw new Error(`${code}: ${msg}`);
|
||||
}
|
||||
|
||||
return (json?.data ?? json) as T;
|
||||
}
|
||||
|
||||
|
||||
// Helper de paginação (Range/Range-Unit)
|
||||
function rangeHeaders(page?: number, limit?: number): Record<string, string> {
|
||||
if (!page || !limit) return {};
|
||||
@ -192,6 +229,68 @@ export async function listarPacientes(params?: {
|
||||
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> {
|
||||
const url = `${REST}/patients?id=eq.${id}`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
@ -262,34 +361,189 @@ export async function listarMedicos(params?: {
|
||||
return await parse<Medico[]>(res);
|
||||
}
|
||||
|
||||
export async function buscarMedicoPorId(id: string | number): Promise<Medico> {
|
||||
const url = `${REST}/doctors?id=eq.${id}`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const arr = await parse<Medico[]>(res);
|
||||
if (!arr?.length) throw new Error("404: Médico não encontrado");
|
||||
return arr[0];
|
||||
// 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> {
|
||||
// Primeiro tenta buscar no Supabase (dados reais)
|
||||
try {
|
||||
const url = `${REST}/doctors?id=eq.${id}`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const arr = await parse<Medico[]>(res);
|
||||
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];
|
||||
}
|
||||
} 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> {
|
||||
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, {
|
||||
method: "POST",
|
||||
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> {
|
||||
const url = `${REST}/doctors?id=eq.${id}`;
|
||||
const res = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
const arr = await parse<Medico[] | Medico>(res);
|
||||
return Array.isArray(arr) ? arr[0] : (arr as 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}`;
|
||||
console.log(`🌐 URL de atualização: ${url}`);
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
|
||||
body: JSON.stringify(cleanPayload),
|
||||
});
|
||||
|
||||
console.log(`📡 Resposta do servidor: ${res.status} ${res.statusText}`);
|
||||
|
||||
if (res.ok) {
|
||||
const arr = await parse<Medico[] | Medico>(res);
|
||||
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> {
|
||||
|
||||
139
susconecta/package-lock.json
generated
139
susconecta/package-lock.json
generated
@ -4086,6 +4086,53 @@
|
||||
"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": {
|
||||
"version": "1.0.30001739",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
|
||||
@ -4578,10 +4625,51 @@
|
||||
"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": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
>>>>>>> origin/develop
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -4727,6 +4815,8 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.24.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
||||
@ -4796,6 +4886,7 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
@ -4814,6 +4905,8 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/es-iterator-helpers": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
|
||||
@ -4842,6 +4935,7 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
@ -4854,6 +4948,8 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.39.10",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
|
||||
@ -5767,6 +5864,8 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/function.prototype.name": {
|
||||
"version": "1.1.8",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/functions-have-names": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||
@ -5806,6 +5906,8 @@
|
||||
"next": ">=13.2.0"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/generator-function": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
|
||||
@ -5816,6 +5918,7 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@ -5862,6 +5965,8 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/get-symbol-description": {
|
||||
"version": "1.1.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@ -5955,6 +6061,8 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/graphemer": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
@ -5985,6 +6093,7 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
|
||||
@ -6013,6 +6124,7 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@ -6182,6 +6294,8 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.5",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/is-date-object": {
|
||||
"version": "1.1.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@ -6454,6 +6571,7 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||
@ -6472,6 +6590,8 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/is-set": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
|
||||
@ -6630,6 +6750,7 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/jiti": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||
@ -7091,6 +7212,8 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@ -7141,6 +7264,7 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
@ -7342,6 +7466,8 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
@ -7355,6 +7481,7 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/object-is": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
|
||||
@ -7380,6 +7507,8 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/object.assign": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
|
||||
@ -7538,6 +7667,7 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
@ -8070,6 +8200,8 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/regexp-tree": {
|
||||
"version": "0.1.27",
|
||||
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
|
||||
@ -8080,6 +8212,7 @@
|
||||
"regexp-tree": "bin/regexp-tree"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.4",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/regjsparser": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
|
||||
@ -8126,6 +8261,7 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
@ -8333,6 +8469,8 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"node_modules/set-proto": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
|
||||
@ -8490,6 +8628,7 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
>>>>>>> origin/develop
|
||||
"node_modules/signature_pad": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-2.3.2.tgz",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user