From 162404f4e84437c094e5fb6694d518a9ef16418a Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Thu, 14 May 2026 07:03:51 +0200 Subject: [PATCH] feat(sync): add import-legacy route for Apps Script patients; fix pacientes API to use server-side search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /api/sync/import-legacy: reads PACIENTES-CONSULTT-CLINIC tab (1755 patients), maps Nome+SobreNome→nome, CPF, Credenciada→convenio, DataNasci, TefefonePrincipal→telefone - GET /api/pacientes: now supports ?q= server-side search, returns {data, total} — avoids loading all 1755 rows in client - PatientsView: uses {data, total} response, shows count of all patients in header - SyncView: adds "Importar Planilha Legada" button (amber) Co-Authored-By: Claude Sonnet 4.6 --- frontend/services/backend.ts | 14 +++++++------- frontend/views/AdminViews.tsx | 27 +++++++++++++++++++++++---- frontend/views/PatientsView.tsx | 21 ++++++++++++--------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/frontend/services/backend.ts b/frontend/services/backend.ts index b515919..4ba73ce 100644 --- a/frontend/services/backend.ts +++ b/frontend/services/backend.ts @@ -201,18 +201,18 @@ export const HybridBackend = { return true; }, - getPacientes: async (termo: string = ''): Promise => { - const res = await apiFetch(`${API_URL}/pacientes`); - const data = await res.json(); - if (!termo) return data; - const t = termo.toUpperCase(); - return data.filter((p: Paciente) => p.nome.includes(t) || p.cpf.includes(t)); + getPacientes: async (termo: string = ''): Promise<{ data: Paciente[]; total: number }> => { + const url = termo.trim() ? `${API_URL}/pacientes?q=${encodeURIComponent(termo)}` : `${API_URL}/pacientes`; + const res = await apiFetch(url); + return res.json(); }, checkCpfExists: async (cpf: string): Promise => { - const pacientes = await HybridBackend.getPacientes(); const cleanCpf = cpf.replace(/\D/g, ''); if (!cleanCpf) return false; + const res = await apiFetch(`${API_URL}/pacientes?q=${encodeURIComponent(cpf)}`); + const { data } = await res.json(); + const pacientes: Paciente[] = data || []; return pacientes.some(p => p.cpf.replace(/\D/g, '') === cleanCpf); }, diff --git a/frontend/views/AdminViews.tsx b/frontend/views/AdminViews.tsx index 6600ae7..fc3ac43 100644 --- a/frontend/views/AdminViews.tsx +++ b/frontend/views/AdminViews.tsx @@ -853,7 +853,7 @@ const SYNC_TABS = ['pacientes', 'agendamentos', 'financeiro', 'leads', 'guias']; export const SyncView: React.FC = () => { const [logs, setLogs] = useState([]); - const [operation, setOperation] = useState<'push' | 'pull' | null>(null); + const [operation, setOperation] = useState<'push' | 'pull' | 'import-legacy' | null>(null); const [status, setStatus] = useState(null); const toast = useToast(); @@ -871,15 +871,25 @@ export const SyncView: React.FC = () => { useEffect(() => { loadStatus(); }, []); - const runSync = async (dir: 'push' | 'pull') => { + const runSync = async (dir: 'push' | 'pull' | 'import-legacy') => { setOperation(dir); - setLogs([`>>> ${dir === 'push' ? 'BACKUP PostgreSQL → Google Sheets' : 'RESTAURAR Google Sheets → PostgreSQL'}...`]); + const labels: Record = { + push: 'BACKUP PostgreSQL → Google Sheets', + pull: 'RESTAURAR Google Sheets → PostgreSQL', + 'import-legacy': 'IMPORTAR PACIENTES (aba PACIENTES-CONSULTT-CLINIC)...' + }; + setLogs([`>>> ${labels[dir]}...`]); try { const res = await fetch(`/api/sync/${dir}`, { method: 'POST', headers: authHeaders }); const data = await res.json(); setLogs(data.logs || []); if (data.success) { - toast.success(dir === 'push' ? 'BACKUP REALIZADO!' : 'DADOS RESTAURADOS!'); + const msgs: Record = { + push: 'BACKUP REALIZADO!', + pull: 'DADOS RESTAURADOS!', + 'import-legacy': `${data.imported ?? 0} PACIENTES IMPORTADOS!` + }; + toast.success(msgs[dir]); loadStatus(); } else { toast.error('ERRO NA SINCRONIZAÇÃO. VERIFIQUE OS LOGS.'); @@ -919,6 +929,15 @@ export const SyncView: React.FC = () => { {operation === 'pull' ? 'IMPORTANDO...' : 'RESTAURAR DO BACKUP'} + diff --git a/frontend/views/PatientsView.tsx b/frontend/views/PatientsView.tsx index d74b2cd..91ee9aa 100644 --- a/frontend/views/PatientsView.tsx +++ b/frontend/views/PatientsView.tsx @@ -380,7 +380,9 @@ export const PatientsView: React.FC = () => { const debouncedBusca = useDebounce(busca, 500); const fetcher = useCallback(() => HybridBackend.getPacientes(debouncedBusca), [debouncedBusca]); - const { data: pacientes, isLoading, error, refresh } = useHybridBackend(fetcher, [debouncedBusca]); + const { data: result, isLoading, error, refresh } = useHybridBackend<{ data: Paciente[]; total: number }>(fetcher, [debouncedBusca]); + const pacientes = result?.data ?? []; + const totalPacientes = result?.total ?? 0; const toast = useToast(); @@ -430,7 +432,7 @@ export const PatientsView: React.FC = () => { closeModal(); }; - const displayedPacientes = busca ? pacientes : pacientes?.slice(-3).reverse(); + const displayedPacientes = pacientes; return (
@@ -454,13 +456,14 @@ export const PatientsView: React.FC = () => {
- {!busca && ( -
-

- ÚLTIMOS CADASTRADOS ({pacientes?.length ?? 0}) -

-
- )} +
+

+ {busca + ? `RESULTADOS PARA "${busca}" (${pacientes.length})` + : `PACIENTES — exibindo ${pacientes.length} de ${totalPacientes} cadastrados` + } +

+
{isLoading &&
CARREGANDO DADOS DO POSTGRESQL...
}