diff --git a/susconecta/app/(main-routes)/calendar/page.tsx b/susconecta/app/(main-routes)/calendar/page.tsx
index aa7380c..30bccef 100644
--- a/susconecta/app/(main-routes)/calendar/page.tsx
+++ b/susconecta/app/(main-routes)/calendar/page.tsx
@@ -2,26 +2,16 @@
// Imports mantidos
import { useEffect, useState } from "react";
-import dynamic from "next/dynamic";
-import Link from "next/link";
// --- Imports do EventManager (NOVO) - MANTIDOS ---
import { EventManager, type Event } from "@/components/features/general/event-manager";
import { v4 as uuidv4 } from 'uuid'; // Usado para IDs de fallback
// Imports mantidos
-import { Sidebar } from "@/components/layout/sidebar";
-import { PagesHeader } from "@/components/features/dashboard/header";
import { Button } from "@/components/ui/button";
import { useAuth } from "@/hooks/useAuth";
import { mockWaitingList } from "@/lib/mocks/appointment-mocks";
import "./index.css";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
import { ThreeDWallCalendar, CalendarEvent } from "@/components/ui/three-dwall-calendar"; // Calendário 3D mantido
import { PatientRegistrationForm } from "@/components/features/forms/patient-registration-form";
@@ -33,11 +23,24 @@ const ListaEspera = dynamic(
export default function AgendamentoPage() {
const { user, token } = useAuth();
const [appointments, setAppointments] = useState([]);
- const [waitingList, setWaitingList] = useState(mockWaitingList);
- const [activeTab, setActiveTab] = useState<"calendar" | "espera" | "3d">("calendar");
-
+ const [activeTab, setActiveTab] = useState<"calendar" | "3d">("calendar");
const [threeDEvents, setThreeDEvents] = useState([]);
+ // Padroniza idioma da página para pt-BR (afeta componentes que usam o lang do documento)
+ useEffect(() => {
+ try {
+ // Atributos no
+ document.documentElement.lang = "pt-BR";
+ document.documentElement.setAttribute("xml:lang", "pt-BR");
+ document.documentElement.setAttribute("data-lang", "pt-BR");
+ // Cookie de locale (usado por apps com i18n)
+ const oneYear = 60 * 60 * 24 * 365;
+ document.cookie = `NEXT_LOCALE=pt-BR; Path=/; Max-Age=${oneYear}; SameSite=Lax`;
+ } catch {
+ // ignore
+ }
+ }, []);
+
// --- NOVO ESTADO ---
// Estado para alimentar o NOVO EventManager com dados da API
const [managerEvents, setManagerEvents] = useState([]);
@@ -48,15 +51,8 @@ export default function AgendamentoPage() {
useEffect(() => {
document.addEventListener("keydown", (event) => {
- if (event.key === "c") {
- setActiveTab("calendar");
- }
- if (event.key === "f") {
- setActiveTab("espera");
- }
- if (event.key === "3") {
- setActiveTab("3d");
- }
+ if (event.key === "c") setActiveTab("calendar");
+ if (event.key === "3") setActiveTab("3d");
});
}, []);
@@ -92,18 +88,23 @@ export default function AgendamentoPage() {
const patient = (patientsById[String(obj.patient_id)]?.full_name) || obj.patient_name || obj.patient_full_name || obj.patient || 'Paciente';
const title = `${patient}: ${obj.appointment_type ?? obj.type ?? ''}`.trim();
-
- let color = "gray"; // Cor padrão
- if (obj.status === 'confirmed') color = 'green';
- if (obj.status === 'pending') color = 'orange';
+
+ // Mapeamento de cores padronizado:
+ // azul = solicitado; verde = confirmado; laranja = pendente; vermelho = cancelado; azul como fallback
+ const status = String(obj.status || "").toLowerCase();
+ let color: Event["color"] = "blue";
+ if (status === "confirmed" || status === "confirmado") color = "green";
+ else if (status === "pending" || status === "pendente") color = "orange";
+ else if (status === "canceled" || status === "cancelado" || status === "cancelled") color = "red";
+ else if (status === "requested" || status === "solicitado") color = "blue";
return {
- id: obj.id || uuidv4(), // Usa ID da API ou gera um
- title: title,
- description: `Agendamento para ${patient}. Status: ${obj.status || 'N/A'}.`,
+ id: obj.id || uuidv4(),
+ title,
+ description: `Agendamento para ${patient}. Status: ${obj.status || 'N/A'}.`,
startTime: start,
endTime: end,
- color: color,
+ color,
};
});
setManagerEvents(newManagerEvents);
@@ -152,10 +153,6 @@ export default function AgendamentoPage() {
}
};
- const handleNotifyPatient = (patientId: string) => {
- console.log(`Notificando paciente ${patientId}`);
- };
-
const handleAddEvent = (event: CalendarEvent) => {
setThreeDEvents((prev) => [...prev, event]);
};
@@ -178,26 +175,10 @@ export default function AgendamentoPage() {
Navegue através dos atalhos: Calendário (C), Fila de espera (F) ou 3D (3).
-
-
-
- Opções »
-
-
-
- Agendamento
-
-
- Procedimento
-
-
- Financeiro
-
-
-
-
+
+
+
+
-
+ {/* Legenda de status (estilo Google Calendar) */}
+
+
+
+
+ Solicitado
+
+
+
+ Confirmado
@@ -251,14 +239,7 @@ export default function AgendamentoPage() {
onOpenAddPatientForm={() => setShowPatientForm(true)}
/>
- ) : (
- // A Lista de Espera foi MANTIDA
- {}}
- />
- )}
+ ) : null}
{/* Formulário de Registro de Paciente */}
diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx
index d44be28..a6e8ccb 100644
--- a/susconecta/app/paciente/page.tsx
+++ b/susconecta/app/paciente/page.tsx
@@ -18,7 +18,8 @@ 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 } from '@/lib/api'
+import { buscarPacientes, buscarPacientePorUserId, getUserInfo, listarAgendamentos, buscarMedicosPorIds, buscarMedicos, atualizarPaciente, buscarPacientePorId, getDoctorById, atualizarAgendamento, deletarAgendamento } 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'
import { listarRelatoriosPorPaciente } from '@/lib/reports'
@@ -35,7 +36,6 @@ const strings = {
ultimosExames: 'Últimos Exames',
mensagensNaoLidas: 'Mensagens Não Lidas',
agendar: 'Agendar',
- reagendar: 'Reagendar',
cancelar: 'Cancelar',
detalhes: 'Detalhes',
adicionarCalendario: 'Adicionar ao calendário',
@@ -445,11 +445,10 @@ export default function PacientePage() {
-
+
)
}
- // Consultas fictícias
const [currentDate, setCurrentDate] = useState(new Date())
// helper: produce a local YYYY-MM-DD key (uses local timezone, not toISOString UTC)
@@ -519,10 +518,15 @@ export default function PacientePage() {
const selectedDate = new Date(currentDate); selectedDate.setHours(0, 0, 0, 0);
const isSelectedDateToday = selectedDate.getTime() === today.getTime()
- // Appointments state (loaded when component mounts)
- const [appointments, setAppointments] = useState(null)
- const [loadingAppointments, setLoadingAppointments] = useState(false)
- const [appointmentsError, setAppointmentsError] = useState(null)
+ // Appointments state (loaded when component mounts)
+ const [appointments, setAppointments] = useState(null)
+ const [doctorsMap, setDoctorsMap] = useState>({}) // Store doctor info by ID
+ const [loadingAppointments, setLoadingAppointments] = useState(false)
+ const [appointmentsError, setAppointmentsError] = useState(null)
+ // expanded appointment id for inline details (kept for possible fallback)
+ const [expandedId, setExpandedId] = useState(null)
+ // selected appointment for modal details
+ const [selectedAppointment, setSelectedAppointment] = useState(null)
useEffect(() => {
let mounted = true
@@ -608,6 +612,7 @@ export default function PacientePage() {
}
})
+ setDoctorsMap(doctorsMap)
setAppointments(mapped)
} catch (err: any) {
console.warn('[Consultas] falha ao carregar agendamentos', err)
@@ -638,6 +643,60 @@ export default function PacientePage() {
const _dialogSource = (appointments !== null ? appointments : consultasFicticias)
const _todaysAppointments = (_dialogSource || []).filter((c: any) => c.data === todayStr)
+ // helper: present a localized label for appointment status
+ const statusLabel = (s: any) => {
+ const raw = (s === null || s === undefined) ? '' : String(s)
+ const key = raw.toLowerCase()
+ const map: Record = {
+ 'requested': 'Solicitado',
+ 'request': 'Solicitado',
+ 'confirmed': 'Confirmado',
+ 'confirmada': 'Confirmada',
+ 'confirmado': 'Confirmado',
+ 'completed': 'Concluído',
+ 'concluído': 'Concluído',
+ 'cancelled': 'Cancelado',
+ 'cancelada': 'Cancelada',
+ 'cancelado': 'Cancelado',
+ 'pending': 'Pendente',
+ 'pendente': 'Pendente',
+ 'checked_in': 'Registrado',
+ 'in_progress': 'Em andamento',
+ 'no_show': 'Não compareceu'
+ }
+ return map[key] || raw
+ }
+
+ // map an appointment (row) to the CalendarRegistrationForm's formData shape
+ const mapAppointmentToFormData = (appointment: any) => {
+ // Use the raw appointment with all fields: doctor_id, scheduled_at, appointment_type, etc.
+ const schedIso = appointment.scheduled_at || (appointment.data && appointment.hora ? `${appointment.data}T${appointment.hora}` : null) || null
+ const baseDate = schedIso ? new Date(schedIso) : new Date()
+ const appointmentDate = schedIso ? baseDate.toISOString().split('T')[0] : ''
+ const startTime = schedIso ? baseDate.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : (appointment.hora || '')
+ const duration = appointment.duration_minutes ?? appointment.duration ?? 30
+
+ // Get doctor name from doctorsMap if available
+ const docName = appointment.medico || (appointment.doctor_id ? doctorsMap[String(appointment.doctor_id)]?.full_name : null) || appointment.doctor_name || appointment.professional_name || '---'
+
+ return {
+ id: appointment.id,
+ patientName: docName,
+ patientId: null,
+ doctorId: appointment.doctor_id ?? null,
+ professionalName: docName,
+ appointmentDate,
+ startTime,
+ endTime: '',
+ status: appointment.status || undefined,
+ appointmentType: appointment.appointment_type || appointment.type || (appointment.local ? 'presencial' : 'teleconsulta'),
+ duration_minutes: duration,
+ notes: appointment.notes || '',
+ }
+ }
+
+
+
return (
{/* Hero Section */}
@@ -771,7 +830,7 @@ export default function PacientePage() {
? 'bg-linear-to-r from-amber-500 to-amber-600 shadow-amber-500/20'
: 'bg-linear-to-r from-red-500 to-red-600 shadow-red-500/20'
}`}>
- {consulta.status}
+ {statusLabel(consulta.status)}
@@ -781,28 +840,43 @@ export default function PacientePage() {
type="button"
size="sm"
className="border border-primary/30 text-primary bg-primary/5 hover:bg-primary! hover:text-white! hover:border-primary! transition-all duration-200 focus-visible:ring-2 focus-visible:ring-primary/40 active:scale-95 text-xs font-semibold flex-1"
+ onClick={() => setSelectedAppointment(consulta)}
>
Detalhes
- {consulta.status !== 'Cancelada' && (
-
- )}
+ {/* Reagendar removed by request */}
{consulta.status !== 'Cancelada' && (
)}
+
+ {/* Inline detalhes removed: modal will show details instead */}
+
))
@@ -811,6 +885,45 @@ export default function PacientePage() {
+
+
+
+
+
+ {/* Reagendar feature removed */}
+
)
}
@@ -1262,7 +1375,7 @@ export default function PacientePage() {
setReportsPage(1)
}, [reports])
- return (
+ return (<>
Laudos
@@ -1334,10 +1447,13 @@ export default function PacientePage() {
)}
+
+
+
-
+ >
)
}
diff --git a/susconecta/app/paciente/resultados/ResultadosClient.tsx b/susconecta/app/paciente/resultados/ResultadosClient.tsx
index 1fa8519..b471740 100644
--- a/susconecta/app/paciente/resultados/ResultadosClient.tsx
+++ b/susconecta/app/paciente/resultados/ResultadosClient.tsx
@@ -148,7 +148,7 @@ export default function ResultadosClient() {
try {
setLoadingMedicos(true)
console.log('[ResultadosClient] Initial doctors fetch starting')
- const list = await buscarMedicos('medico').catch((err) => {
+ const list = await buscarMedicos('').catch((err) => {
console.error('[ResultadosClient] Initial fetch error:', err)
return []
})
@@ -175,7 +175,7 @@ export default function ResultadosClient() {
setAgendaByDoctor({})
setAgendasExpandida({})
// termo de busca: usar a especialidade escolhida
- const termo = (especialidadeHero && especialidadeHero !== 'Veja mais') ? especialidadeHero : 'medico'
+ const termo = (especialidadeHero && especialidadeHero !== 'Veja mais') ? especialidadeHero : ''
console.log('[ResultadosClient] Fetching doctors with term:', termo)
const list = await buscarMedicos(termo).catch((err) => {
console.error('[ResultadosClient] buscarMedicos error:', err)
@@ -219,9 +219,9 @@ export default function ResultadosClient() {
}, [searchQuery])
// 3) Carregar horários disponíveis para um médico (próximos 7 dias) e agrupar por dia
- async function loadAgenda(doctorId: string) {
- if (!doctorId) return
- if (agendaLoading[doctorId]) return
+ async function loadAgenda(doctorId: string): Promise<{ iso: string; label: string } | null> {
+ if (!doctorId) return null
+ if (agendaLoading[doctorId]) return null
setAgendaLoading((s) => ({ ...s, [doctorId]: true }))
try {
// janela de 7 dias
@@ -271,10 +271,12 @@ export default function ResultadosClient() {
nearest = { iso: s.iso, label: s.label }
}
- setAgendaByDoctor((prev) => ({ ...prev, [doctorId]: days }))
- setNearestSlotByDoctor((prev) => ({ ...prev, [doctorId]: nearest }))
+ setAgendaByDoctor((prev) => ({ ...prev, [doctorId]: days }))
+ setNearestSlotByDoctor((prev) => ({ ...prev, [doctorId]: nearest }))
+ return nearest
} catch (e: any) {
showToast('error', e?.message || 'Falha ao buscar horários')
+ return null
} finally {
setAgendaLoading((s) => ({ ...s, [doctorId]: false }))
}
@@ -752,19 +754,7 @@ export default function ResultadosClient() {
-
-
- {/* Search input para buscar médico por nome */}
+ {/* Search input para buscar médico por nome (movido antes do Select de bairro para ficar ao lado visualmente) */}
+
+
))}
{hours.map((hour) => (
- <>
+
)
})}
- >
+
))}
@@ -1401,15 +1138,14 @@ function ListView({
const groupedEvents = sortedEvents.reduce(
(acc, event) => {
- const dateKey = event.startTime.toLocaleDateString("pt-BR", {
+ const dateKey = event.startTime.toLocaleDateString(LOCALE, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
+ timeZone: TIMEZONE,
})
- if (!acc[dateKey]) {
- acc[dateKey] = []
- }
+ if (!acc[dateKey]) acc[dateKey] = []
acc[dateKey].push(event)
return acc
},
@@ -1426,11 +1162,7 @@ function ListView({
{dateEvents.map((event) => {
const colorClasses = getColorClasses(event.color)
return (
- onEventClick(event)}
- className="group cursor-pointer rounded-lg border bg-card p-3 transition-all hover:shadow-md hover:scale-[1.01] animate-in fade-in slide-in-from-bottom-2 duration-300 sm:p-4"
- >
+
onEventClick(event)} className="group cursor-pointer rounded-lg border bg-card p-3 transition-all hover:shadow-md hover:scale-[1.01] animate-in fade-in slide-in-from-bottom-2 duration-300 sm:p-4">
@@ -1456,7 +1188,9 @@ function ListView({
- {event.startTime.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" })} - {event.endTime.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" })}
+ {event.startTime.toLocaleTimeString(LOCALE, { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: TIMEZONE })}
+ {" - "}
+ {event.endTime.toLocaleTimeString(LOCALE, { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: TIMEZONE })}
{event.tags && event.tags.length > 0 && (