Merge claude/youthful-mendel-0c569f: import-legacy + server-side pacientes search

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-05-14 07:03:58 +02:00
3 changed files with 42 additions and 20 deletions
+7 -7
View File
@@ -201,18 +201,18 @@ export const HybridBackend = {
return true;
},
getPacientes: async (termo: string = ''): Promise<Paciente[]> => {
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<boolean> => {
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);
},
+23 -4
View File
@@ -853,7 +853,7 @@ const SYNC_TABS = ['pacientes', 'agendamentos', 'financeiro', 'leads', 'guias'];
export const SyncView: React.FC = () => {
const [logs, setLogs] = useState<string[]>([]);
const [operation, setOperation] = useState<'push' | 'pull' | null>(null);
const [operation, setOperation] = useState<'push' | 'pull' | 'import-legacy' | null>(null);
const [status, setStatus] = useState<any>(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<string, string> = {
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<string, string> = {
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 = () => {
<Database size={15} className={operation === 'pull' ? 'animate-spin' : ''} />
{operation === 'pull' ? 'IMPORTANDO...' : 'RESTAURAR DO BACKUP'}
</button>
<button
onClick={() => runSync('import-legacy')}
disabled={!!operation}
title="Importa pacientes da aba PACIENTES-CONSULTT-CLINIC (Apps Script)"
className={`px-4 py-2 rounded-xl flex items-center gap-2 text-xs font-black uppercase transition-all shadow-sm ${operation ? 'bg-gray-200 text-gray-400 cursor-not-allowed' : 'bg-amber-500 hover:bg-amber-600 text-white shadow-amber-100'}`}
>
<Database size={15} className={operation === 'import-legacy' ? 'animate-spin' : ''} />
{operation === 'import-legacy' ? 'IMPORTANDO...' : 'IMPORTAR PLANILHA LEGADA'}
</button>
</div>
</PageHeader>
+12 -9
View File
@@ -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<Paciente[]>(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 (
<div className="h-full flex flex-col relative">
@@ -454,13 +456,14 @@ export const PatientsView: React.FC = () => {
</div>
</div>
{!busca && (
<div className="mb-4">
<h3 className="text-sm font-bold text-gray-500 uppercase">
ÚLTIMOS CADASTRADOS ({pacientes?.length ?? 0})
</h3>
</div>
)}
<div className="mb-4">
<h3 className="text-sm font-bold text-gray-500 uppercase">
{busca
? `RESULTADOS PARA "${busca}" (${pacientes.length})`
: `PACIENTES — exibindo ${pacientes.length} de ${totalPacientes} cadastrados`
}
</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 overflow-y-auto pb-10">
{isLoading && <div className="col-span-3 text-center p-10 text-gray-500 uppercase font-bold flex justify-center items-center gap-2"><Loader2 className="animate-spin" /> CARREGANDO DADOS DO POSTGRESQL...</div>}