From e0be095055214e284fc32e8bd59a6440f8aed11d Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Thu, 14 May 2026 07:50:22 +0200 Subject: [PATCH] fix(patients): prevent $.map crash in AgendamentoModal and AutocompleteField MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix agendamentos?.filter(...).map(...) — optional chain didn't protect .map, crashing when agendamentos is null on first render - Add Array.isArray guard in AutocompleteField before setResults to prevent non-array from backend error responses breaking subsequent .map - Add array guard in getGruposFamiliares to return [] on backend error instead of propagating error object Co-Authored-By: Claude Sonnet 4.6 --- frontend/services/backend.ts | 5 +++-- frontend/views/PatientsView.tsx | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/services/backend.ts b/frontend/services/backend.ts index ccdc240..b3b56b4 100644 --- a/frontend/services/backend.ts +++ b/frontend/services/backend.ts @@ -239,10 +239,11 @@ export const HybridBackend = { return await res.json(); }, - getGruposFamiliares: async (q?: string) => { + getGruposFamiliares: async (q?: string): Promise => { const url = q?.trim() ? `${API_URL}/grupos-familiares?q=${encodeURIComponent(q)}` : `${API_URL}/grupos-familiares`; const res = await apiFetch(url); - return res.json(); + const data = await res.json(); + return Array.isArray(data) ? data : []; }, criarGrupoFamiliar: async (nome: string, responsavel_id?: string) => { diff --git a/frontend/views/PatientsView.tsx b/frontend/views/PatientsView.tsx index d7d6ecc..7e3a5cc 100644 --- a/frontend/views/PatientsView.tsx +++ b/frontend/views/PatientsView.tsx @@ -36,7 +36,7 @@ function AutocompleteField({ useEffect(() => { if (!debounced.trim() || value) { setResults([]); setOpen(false); return; } setLoading(true); - fetchFn(debounced).then(r => { setResults(r); setOpen(r.length > 0); }).finally(() => setLoading(false)); + fetchFn(debounced).then(r => { const arr = Array.isArray(r) ? r : []; setResults(arr); setOpen(arr.length > 0); }).finally(() => setLoading(false)); }, [debounced]); useEffect(() => { @@ -167,13 +167,13 @@ const AgendamentoModal: React.FC<{ patient: Paciente, onClose: () => void }> = ( const slots: { time: string, available: boolean }[] = []; if (!selectedDentistId || !selectedDate) return slots; const startTime = 8 * 60, endTime = 18 * 60, lunchStart = 12 * 60, lunchEnd = 14 * 60; - const existingAppointments = agendamentos?.filter(ag => + const existingAppointments = (agendamentos ?? []).filter(ag => ag.dentistaId === selectedDentistId && new Date(ag.start).toISOString().split('T')[0] === selectedDate ).map(ag => ({ start: new Date(ag.start).getHours() * 60 + new Date(ag.start).getMinutes(), end: new Date(ag.end).getHours() * 60 + new Date(ag.end).getMinutes(), - })) || []; + })); for (let time = startTime; time < endTime; time += duration) { const slotStart = time, slotEnd = time + duration;