From 8d1473a14888df6468d86c2d8d94793267729099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 2 Oct 2025 01:49:54 -0300 Subject: [PATCH] add-doctor-id --- .../app/(main-routes)/doutores/page.tsx | 229 ++++++++++++++++-- susconecta/components/dashboard/header.tsx | 7 +- susconecta/lib/api.ts | 64 +++++ 3 files changed, 270 insertions(+), 30 deletions(-) diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index feb1798..81cf008 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -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(null); const [viewingDoctor, setViewingDoctor] = useState(null); + const [searchResults, setSearchResults] = useState([]); + const [searchMode, setSearchMode] = useState(false); + const [searchTimeout, setSearchTimeout] = useState(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) { + 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) { + 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); @@ -156,14 +315,36 @@ setDoctors((list ?? []).map(normalizeMedico));
-
- - setSearch(e.target.value)} - /> +
+
+ + +
+ + {searchMode && ( + + )}
diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index 3e95f76..cec8da5 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -361,6 +361,70 @@ export async function listarMedicos(params?: { return await parse(res); } +// Nova função para busca avançada de médicos +export async function buscarMedicos(termo: string): Promise { + 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(); + + // 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(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 { const url = `${REST}/doctors?id=eq.${id}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() });