From 2cc3687628cd93c73f850271e467cc7e5990cc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:58:39 -0300 Subject: [PATCH] fix-delete-appoiment --- .../app/(main-routes)/consultas/page.tsx | 4 +- susconecta/app/paciente/page.tsx | 17 ++- susconecta/lib/api.ts | 126 ++++++++++++++++-- 3 files changed, 135 insertions(+), 12 deletions(-) diff --git a/susconecta/app/(main-routes)/consultas/page.tsx b/susconecta/app/(main-routes)/consultas/page.tsx index 0ce1acc..8629de1 100644 --- a/susconecta/app/(main-routes)/consultas/page.tsx +++ b/susconecta/app/(main-routes)/consultas/page.tsx @@ -55,7 +55,7 @@ import { } from "@/components/ui/select"; import { mockProfessionals } from "@/lib/mocks/appointment-mocks"; -import { listarAgendamentos, buscarPacientesPorIds, buscarMedicosPorIds, atualizarAgendamento, buscarAgendamentoPorId, deletarAgendamento } from "@/lib/api"; +import { listarAgendamentos, buscarPacientesPorIds, buscarMedicosPorIds, atualizarAgendamento, buscarAgendamentoPorId, deletarAgendamento, addDeletedAppointmentId } from "@/lib/api"; import { CalendarRegistrationForm } from "@/components/features/forms/calendar-registration-form"; const formatDate = (date: string | Date) => { @@ -140,6 +140,8 @@ export default function ConsultasPage() { try { // call server DELETE await deletarAgendamento(appointmentId); + // Mark as deleted in cache so it won't appear again + addDeletedAppointmentId(appointmentId); // remove from UI setAppointments((prev) => prev.filter((a) => a.id !== appointmentId)); // also update originalAppointments cache diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index ab0e481..2d16f40 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -18,7 +18,7 @@ import Link from 'next/link' import ProtectedRoute from '@/components/shared/ProtectedRoute' import { useAuth } from '@/hooks/useAuth' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { buscarPacientes, buscarPacientePorUserId, getUserInfo, listarAgendamentos, buscarMedicosPorIds, buscarMedicos, atualizarPaciente, buscarPacientePorId, getDoctorById, atualizarAgendamento, deletarAgendamento } from '@/lib/api' +import { buscarPacientes, buscarPacientePorUserId, getUserInfo, listarAgendamentos, buscarMedicosPorIds, buscarMedicos, atualizarPaciente, buscarPacientePorId, getDoctorById, atualizarAgendamento, deletarAgendamento, addDeletedAppointmentId } from '@/lib/api' import { CalendarRegistrationForm } from '@/components/features/forms/calendar-registration-form' import { buscarRelatorioPorId, listarRelatoriosPorMedico } from '@/lib/reports' import { ENV_CONFIG } from '@/lib/env-config' @@ -610,6 +610,19 @@ export default function PacientePage() { hora: sched ? sched.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '', status: a.status ? String(a.status) : 'Pendente', } + }).filter((consulta: any) => { + // Filter out cancelled appointments (those with cancelled_at set OR status='cancelled') + const raw = rows.find((r: any) => String(r.id) === String(consulta.id)); + if (!raw) return false; + + // Check cancelled_at field + const cancelled = raw.cancelled_at; + if (cancelled && cancelled !== '' && cancelled !== 'null') return false; + + // Check status field + if (raw.status && String(raw.status).toLowerCase() === 'cancelled') return false; + + return true; }) setDoctorsMap(doctorsMap) @@ -856,6 +869,8 @@ export default function PacientePage() { if (!ok) return // call API to delete await deletarAgendamento(consulta.id) + // Mark as deleted in cache so it won't appear again + addDeletedAppointmentId(consulta.id) // remove from local list setAppointments((prev) => { if (!prev) return prev diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index 05e8390..fe7c217 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -1293,7 +1293,23 @@ export async function listarAgendamentos(query?: string): Promise if (!res.ok && res.status === 401) { throw new Error('Não autenticado. Token ausente ou expirado. Faça login novamente.'); } - return await parse(res); + const appointments = await parse(res); + // Filter out soft-deleted appointments (those with cancelled_at set OR status='cancelled' OR in deleted cache) + return appointments.filter((a) => { + const id = String(a.id); + + // Check if in deleted cache + if (deletedAppointmentIds.has(id)) return false; + + // Check cancelled_at field + const cancelled = a.cancelled_at; + if (cancelled && cancelled !== '' && cancelled !== 'null') return false; + + // Check status field + if (a.status && String(a.status).toLowerCase() === 'cancelled') return false; + + return true; + }); } /** @@ -1309,13 +1325,68 @@ export async function buscarAgendamentoPorId(id: string | number, select: string const url = `${REST}/appointments?id=eq.${encodeURIComponent(sId)}&${params.toString()}`; const headers = baseHeaders(); const arr = await fetchWithFallback(url, headers); - if (arr && arr.length) return arr[0]; + // Filter out soft-deleted appointments (those with cancelled_at set OR status='cancelled' OR in deleted cache) + const active = arr?.filter((a) => { + const id = String(a.id); + + // Check if in deleted cache + if (deletedAppointmentIds.has(id)) return false; + + // Check cancelled_at field + const cancelled = a.cancelled_at; + if (cancelled && cancelled !== '' && cancelled !== 'null') return false; + + // Check status field + if (a.status && String(a.status).toLowerCase() === 'cancelled') return false; + + return true; + }); + if (active && active.length) return active[0]; throw new Error('404: Agendamento não encontrado'); } /** * Deleta um agendamento por ID (DELETE /rest/v1/appointments?id=eq.) */ +// Track deleted appointment IDs in localStorage to persist across page reloads +const DELETED_APPOINTMENTS_KEY = 'deleted_appointment_ids'; + +function getDeletedAppointmentIds(): Set { + try { + if (typeof window === 'undefined') return new Set(); + const stored = localStorage.getItem(DELETED_APPOINTMENTS_KEY); + if (stored) { + const ids = JSON.parse(stored); + return new Set(Array.isArray(ids) ? ids : []); + } + } catch (e) { + console.warn('[API] Erro ao ler deleted appointments do localStorage', e); + } + return new Set(); +} + +function saveDeletedAppointmentIds(ids: Set) { + try { + if (typeof window === 'undefined') return; + localStorage.setItem(DELETED_APPOINTMENTS_KEY, JSON.stringify(Array.from(ids))); + } catch (e) { + console.warn('[API] Erro ao salvar deleted appointments no localStorage', e); + } +} + +const deletedAppointmentIds = getDeletedAppointmentIds(); + +export function addDeletedAppointmentId(id: string | number) { + const idStr = String(id); + deletedAppointmentIds.add(idStr); + saveDeletedAppointmentIds(deletedAppointmentIds); +} + +export function clearDeletedAppointments() { + deletedAppointmentIds.clear(); + saveDeletedAppointmentIds(deletedAppointmentIds); +} + export async function deletarAgendamento(id: string | number): Promise { if (!id) throw new Error('ID do agendamento é obrigatório'); const url = `${REST}/appointments?id=eq.${encodeURIComponent(String(id))}`; @@ -1325,9 +1396,11 @@ export async function deletarAgendamento(id: string | number): Promise { headers: withPrefer({ ...baseHeaders() }, 'return=minimal'), }); - if (res.status === 204) return; - // Some deployments may return 200 with a representation — accept that too - if (res.status === 200) return; + if (res.status === 204 || res.status === 200) { + // Mark as deleted locally AND persist in localStorage + addDeletedAppointmentId(id); + return; + } // Otherwise surface a friendly error using parse() await parse(res as Response); } @@ -3018,7 +3091,8 @@ export async function countAppointmentsToday(): Promise { const today = new Date().toISOString().split('T')[0]; const tomorrow = new Date(Date.now() + 86400000).toISOString().split('T')[0]; - const url = `${REST}/appointments?scheduled_at=gte.${today}T00:00:00&scheduled_at=lt.${tomorrow}T00:00:00&select=id&limit=1`; + // Filter out soft-deleted appointments: cancelled_at is null + const url = `${REST}/appointments?scheduled_at=gte.${today}T00:00:00&scheduled_at=lt.${tomorrow}T00:00:00&cancelled_at=is.null&select=id&limit=1`; const res = await fetch(url, { headers: { ...baseHeaders(), @@ -3045,9 +3119,25 @@ export async function getUpcomingAppointments(limit: number = 10): Promise(res); + const appointments = await parse(res); + // Filter out soft-deleted appointments (those with cancelled_at set OR status='cancelled' OR in deleted cache) + return appointments.filter((a) => { + const id = String(a.id); + + // Check if in deleted cache + if (deletedAppointmentIds.has(id)) return false; + + // Check cancelled_at field + const cancelled = a.cancelled_at; + if (cancelled && cancelled !== '' && cancelled !== 'null') return false; + + // Check status field + if (a.status && String(a.status).toLowerCase() === 'cancelled') return false; + + return true; + }); } catch (err) { console.error('[getUpcomingAppointments] Erro:', err); return []; @@ -3063,9 +3153,25 @@ export async function getAppointmentsByDateRange(days: number = 14): Promise(res); + const appointments = await parse(res); + // Filter out soft-deleted appointments (those with cancelled_at set OR status='cancelled' OR in deleted cache) + return appointments.filter((a) => { + const id = String(a.id); + + // Check if in deleted cache + if (deletedAppointmentIds.has(id)) return false; + + // Check cancelled_at field + const cancelled = a.cancelled_at; + if (cancelled && cancelled !== '' && cancelled !== 'null') return false; + + // Check status field + if (a.status && String(a.status).toLowerCase() === 'cancelled') return false; + + return true; + }); } catch (err) { console.error('[getAppointmentsByDateRange] Erro:', err); return [];