fix-delete-appoiment

This commit is contained in:
João Gustavo 2025-11-06 22:58:39 -03:00
parent dc83db3e7c
commit 2cc3687628
3 changed files with 135 additions and 12 deletions

View File

@ -55,7 +55,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { mockProfessionals } from "@/lib/mocks/appointment-mocks"; 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"; import { CalendarRegistrationForm } from "@/components/features/forms/calendar-registration-form";
const formatDate = (date: string | Date) => { const formatDate = (date: string | Date) => {
@ -140,6 +140,8 @@ export default function ConsultasPage() {
try { try {
// call server DELETE // call server DELETE
await deletarAgendamento(appointmentId); await deletarAgendamento(appointmentId);
// Mark as deleted in cache so it won't appear again
addDeletedAppointmentId(appointmentId);
// remove from UI // remove from UI
setAppointments((prev) => prev.filter((a) => a.id !== appointmentId)); setAppointments((prev) => prev.filter((a) => a.id !== appointmentId));
// also update originalAppointments cache // also update originalAppointments cache

View File

@ -18,7 +18,7 @@ import Link from 'next/link'
import ProtectedRoute from '@/components/shared/ProtectedRoute' import ProtectedRoute from '@/components/shared/ProtectedRoute'
import { useAuth } from '@/hooks/useAuth' import { useAuth } from '@/hooks/useAuth'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' 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 { CalendarRegistrationForm } from '@/components/features/forms/calendar-registration-form'
import { buscarRelatorioPorId, listarRelatoriosPorMedico } from '@/lib/reports' import { buscarRelatorioPorId, listarRelatoriosPorMedico } from '@/lib/reports'
import { ENV_CONFIG } from '@/lib/env-config' 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' }) : '', hora: sched ? sched.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '',
status: a.status ? String(a.status) : 'Pendente', 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) setDoctorsMap(doctorsMap)
@ -856,6 +869,8 @@ export default function PacientePage() {
if (!ok) return if (!ok) return
// call API to delete // call API to delete
await deletarAgendamento(consulta.id) await deletarAgendamento(consulta.id)
// Mark as deleted in cache so it won't appear again
addDeletedAppointmentId(consulta.id)
// remove from local list // remove from local list
setAppointments((prev) => { setAppointments((prev) => {
if (!prev) return prev if (!prev) return prev

View File

@ -1293,7 +1293,23 @@ export async function listarAgendamentos(query?: string): Promise<Appointment[]>
if (!res.ok && res.status === 401) { if (!res.ok && res.status === 401) {
throw new Error('Não autenticado. Token ausente ou expirado. Faça login novamente.'); throw new Error('Não autenticado. Token ausente ou expirado. Faça login novamente.');
} }
return await parse<Appointment[]>(res); const appointments = await parse<Appointment[]>(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 url = `${REST}/appointments?id=eq.${encodeURIComponent(sId)}&${params.toString()}`;
const headers = baseHeaders(); const headers = baseHeaders();
const arr = await fetchWithFallback<Appointment[]>(url, headers); const arr = await fetchWithFallback<Appointment[]>(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'); throw new Error('404: Agendamento não encontrado');
} }
/** /**
* Deleta um agendamento por ID (DELETE /rest/v1/appointments?id=eq.<id>) * Deleta um agendamento por ID (DELETE /rest/v1/appointments?id=eq.<id>)
*/ */
// Track deleted appointment IDs in localStorage to persist across page reloads
const DELETED_APPOINTMENTS_KEY = 'deleted_appointment_ids';
function getDeletedAppointmentIds(): Set<string> {
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<string>) {
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<void> { export async function deletarAgendamento(id: string | number): Promise<void> {
if (!id) throw new Error('ID do agendamento é obrigatório'); if (!id) throw new Error('ID do agendamento é obrigatório');
const url = `${REST}/appointments?id=eq.${encodeURIComponent(String(id))}`; const url = `${REST}/appointments?id=eq.${encodeURIComponent(String(id))}`;
@ -1325,9 +1396,11 @@ export async function deletarAgendamento(id: string | number): Promise<void> {
headers: withPrefer({ ...baseHeaders() }, 'return=minimal'), headers: withPrefer({ ...baseHeaders() }, 'return=minimal'),
}); });
if (res.status === 204) return; if (res.status === 204 || res.status === 200) {
// Some deployments may return 200 with a representation — accept that too // Mark as deleted locally AND persist in localStorage
if (res.status === 200) return; addDeletedAppointmentId(id);
return;
}
// Otherwise surface a friendly error using parse() // Otherwise surface a friendly error using parse()
await parse(res as Response); await parse(res as Response);
} }
@ -3018,7 +3091,8 @@ export async function countAppointmentsToday(): Promise<number> {
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const tomorrow = new Date(Date.now() + 86400000).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, { const res = await fetch(url, {
headers: { headers: {
...baseHeaders(), ...baseHeaders(),
@ -3045,9 +3119,25 @@ export async function getUpcomingAppointments(limit: number = 10): Promise<any[]
const today = new Date().toISOString(); const today = new Date().toISOString();
const nextWeek = new Date(Date.now() + 7 * 86400000).toISOString(); const nextWeek = new Date(Date.now() + 7 * 86400000).toISOString();
const url = `${REST}/appointments?scheduled_at=gte.${today}&scheduled_at=lt.${nextWeek}&order=scheduled_at.asc&limit=${limit}&select=id,scheduled_at,status,doctor_id,patient_id`; const url = `${REST}/appointments?scheduled_at=gte.${today}&scheduled_at=lt.${nextWeek}&order=scheduled_at.asc&limit=${limit}&select=id,scheduled_at,status,doctor_id,patient_id,cancelled_at`;
const res = await fetch(url, { headers: baseHeaders() }); const res = await fetch(url, { headers: baseHeaders() });
return await parse<any[]>(res); const appointments = await parse<any[]>(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) { } catch (err) {
console.error('[getUpcomingAppointments] Erro:', err); console.error('[getUpcomingAppointments] Erro:', err);
return []; return [];
@ -3063,9 +3153,25 @@ export async function getAppointmentsByDateRange(days: number = 14): Promise<any
startDate.setDate(startDate.getDate() - days); startDate.setDate(startDate.getDate() - days);
const endDate = new Date().toISOString(); const endDate = new Date().toISOString();
const url = `${REST}/appointments?scheduled_at=gte.${startDate.toISOString()}&scheduled_at=lt.${endDate}&select=scheduled_at,status&order=scheduled_at.asc`; const url = `${REST}/appointments?scheduled_at=gte.${startDate.toISOString()}&scheduled_at=lt.${endDate}&select=scheduled_at,status,cancelled_at,id&order=scheduled_at.asc`;
const res = await fetch(url, { headers: baseHeaders() }); const res = await fetch(url, { headers: baseHeaders() });
return await parse<any[]>(res); const appointments = await parse<any[]>(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) { } catch (err) {
console.error('[getAppointmentsByDateRange] Erro:', err); console.error('[getAppointmentsByDateRange] Erro:', err);
return []; return [];