Descrição curta do que foi alterado
This commit is contained in:
parent
591d8681ac
commit
6c0c7d75b8
@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Search, Plus, Eye, Edit, Trash2 } from "lucide-react";
|
||||
import { Search, Plus, Eye, Edit, Trash2, X } from "lucide-react";
|
||||
import {
|
||||
appointmentService,
|
||||
type Appointment,
|
||||
@ -25,9 +25,14 @@ export function SecretaryAppointmentList() {
|
||||
const [statusFilter, setStatusFilter] = useState("Todos");
|
||||
const [typeFilter, setTypeFilter] = useState("Todos");
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [modalMode, setModalMode] = useState<"create" | "edit">("create");
|
||||
const [selectedAppointment, setSelectedAppointment] = useState<
|
||||
AppointmentWithDetails | null
|
||||
>(null);
|
||||
const [patients, setPatients] = useState<Patient[]>([]);
|
||||
const [doctors, setDoctors] = useState<Doctor[]>([]);
|
||||
const [formData, setFormData] = useState({
|
||||
const [formData, setFormData] = useState<any>({
|
||||
id: undefined,
|
||||
patient_id: "",
|
||||
doctor_id: "",
|
||||
scheduled_at: "",
|
||||
@ -81,6 +86,28 @@ export function SecretaryAppointmentList() {
|
||||
loadDoctorsAndPatients();
|
||||
}, []);
|
||||
|
||||
// Se outro componente pediu para abrir o modal de criação com paciente pré-selecionado
|
||||
useEffect(() => {
|
||||
const openFromSession = () => {
|
||||
const patientId = sessionStorage.getItem("selectedPatientForAppointment");
|
||||
if (patientId) {
|
||||
setFormData((prev: any) => ({ ...prev, patient_id: patientId }));
|
||||
setModalMode("create");
|
||||
setShowCreateModal(true);
|
||||
sessionStorage.removeItem("selectedPatientForAppointment");
|
||||
}
|
||||
};
|
||||
|
||||
// Try on mount
|
||||
openFromSession();
|
||||
|
||||
// Listen for explicit events
|
||||
const handler = () => openFromSession();
|
||||
window.addEventListener("open-create-appointment", handler);
|
||||
return () => window.removeEventListener("open-create-appointment", handler);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Função de filtro
|
||||
const filteredAppointments = appointments.filter((appointment) => {
|
||||
// Filtro de busca por nome do paciente ou médico
|
||||
@ -91,13 +118,36 @@ export function SecretaryAppointmentList() {
|
||||
appointment.doctor?.full_name?.toLowerCase().includes(searchLower) ||
|
||||
appointment.order_number?.toString().includes(searchTerm);
|
||||
|
||||
// Mapeia o valor selecionado no select para o valor real usado na API/data
|
||||
const mapStatusFilterToValue = (label: string) => {
|
||||
if (label === "Todos") return null;
|
||||
const map: Record<string, string> = {
|
||||
Confirmada: "confirmed",
|
||||
Agendada: "requested",
|
||||
Cancelada: "cancelled",
|
||||
"Concluída": "completed",
|
||||
Concluida: "completed",
|
||||
};
|
||||
return map[label] || label.toLowerCase();
|
||||
};
|
||||
|
||||
const mapTypeFilterToValue = (label: string) => {
|
||||
if (label === "Todos") return null;
|
||||
const map: Record<string, string> = {
|
||||
Presencial: "presencial",
|
||||
Telemedicina: "telemedicina",
|
||||
};
|
||||
return map[label] || label.toLowerCase();
|
||||
};
|
||||
|
||||
const statusValue = mapStatusFilterToValue(statusFilter);
|
||||
const typeValue = mapTypeFilterToValue(typeFilter);
|
||||
|
||||
// Filtro de status
|
||||
const matchesStatus =
|
||||
statusFilter === "Todos" || appointment.status === statusFilter;
|
||||
const matchesStatus = statusValue === null || appointment.status === statusValue;
|
||||
|
||||
// Filtro de tipo
|
||||
const matchesType =
|
||||
typeFilter === "Todos" || appointment.appointment_type === typeFilter;
|
||||
const matchesType = typeValue === null || appointment.appointment_type === typeValue;
|
||||
|
||||
return matchesSearch && matchesStatus && matchesType;
|
||||
});
|
||||
@ -135,16 +185,25 @@ export function SecretaryAppointmentList() {
|
||||
}
|
||||
|
||||
try {
|
||||
await appointmentService.create({
|
||||
patient_id: formData.patient_id,
|
||||
doctor_id: formData.doctor_id,
|
||||
scheduled_at: new Date(formData.scheduled_at).toISOString(),
|
||||
appointment_type: formData.appointment_type as
|
||||
| "presencial"
|
||||
| "telemedicina",
|
||||
});
|
||||
|
||||
toast.success("Consulta agendada com sucesso!");
|
||||
if (modalMode === "edit" && formData.id) {
|
||||
// Update only allowed fields per API types
|
||||
const updatePayload: any = {};
|
||||
if (formData.scheduled_at) updatePayload.scheduled_at = new Date(formData.scheduled_at).toISOString();
|
||||
if (formData.notes) updatePayload.notes = formData.notes;
|
||||
await appointmentService.update(formData.id, updatePayload);
|
||||
toast.success("Consulta atualizada com sucesso!");
|
||||
} else {
|
||||
await appointmentService.create({
|
||||
patient_id: formData.patient_id,
|
||||
doctor_id: formData.doctor_id,
|
||||
scheduled_at: new Date(formData.scheduled_at).toISOString(),
|
||||
appointment_type: formData.appointment_type as
|
||||
| "presencial"
|
||||
| "telemedicina",
|
||||
patient_notes: formData.notes,
|
||||
});
|
||||
toast.success("Consulta agendada com sucesso!");
|
||||
}
|
||||
setShowCreateModal(false);
|
||||
loadAppointments();
|
||||
} catch (error) {
|
||||
@ -157,6 +216,23 @@ export function SecretaryAppointmentList() {
|
||||
loadAppointments();
|
||||
};
|
||||
|
||||
const handleViewAppointment = (appointment: AppointmentWithDetails) => {
|
||||
setSelectedAppointment(appointment);
|
||||
};
|
||||
|
||||
const handleEditAppointment = (appointment: AppointmentWithDetails) => {
|
||||
setModalMode("edit");
|
||||
setFormData({
|
||||
id: appointment.id,
|
||||
patient_id: appointment.patient_id || "",
|
||||
doctor_id: appointment.doctor_id || "",
|
||||
scheduled_at: appointment.scheduled_at || "",
|
||||
appointment_type: appointment.appointment_type || "presencial",
|
||||
notes: appointment.notes || "",
|
||||
});
|
||||
setShowCreateModal(true);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setSearchTerm("");
|
||||
setStatusFilter("Todos");
|
||||
@ -411,12 +487,14 @@ export function SecretaryAppointmentList() {
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleViewAppointment(appointment)}
|
||||
title="Visualizar"
|
||||
className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditAppointment(appointment)}
|
||||
title="Editar"
|
||||
className="p-2 text-orange-600 hover:bg-orange-50 rounded-lg transition-colors"
|
||||
>
|
||||
@ -443,7 +521,7 @@ export function SecretaryAppointmentList() {
|
||||
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
Nova Consulta
|
||||
{modalMode === "edit" ? "Editar Consulta" : "Nova Consulta"}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@ -527,6 +605,22 @@ export function SecretaryAppointmentList() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Observações
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.notes || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, notes: e.target.value })
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 h-24"
|
||||
placeholder="Observações da consulta"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@ -539,13 +633,78 @@ export function SecretaryAppointmentList() {
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
|
||||
>
|
||||
Agendar Consulta
|
||||
{modalMode === "edit" ? "Salvar Alterações" : "Agendar Consulta"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal de Visualizar Consulta */}
|
||||
{selectedAppointment && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6 border-b border-gray-200 flex items-center justify-between">
|
||||
<h2 className="text-2xl font-bold text-gray-900">Visualizar Consulta</h2>
|
||||
<button
|
||||
onClick={() => setSelectedAppointment(null)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Paciente</label>
|
||||
<p className="text-gray-900 font-medium">{selectedAppointment.patient?.full_name || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Médico</label>
|
||||
<p className="text-gray-900">{selectedAppointment.doctor?.full_name || '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Data</label>
|
||||
<p className="text-gray-900">{selectedAppointment.scheduled_at ? formatDate(selectedAppointment.scheduled_at) : '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Hora</label>
|
||||
<p className="text-gray-900">{selectedAppointment.scheduled_at ? formatTime(selectedAppointment.scheduled_at) : '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Tipo</label>
|
||||
<p className="text-gray-900">{selectedAppointment.appointment_type === 'telemedicina' ? 'Telemedicina' : 'Presencial'}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Status</label>
|
||||
<div>{getStatusBadge(selectedAppointment.status || 'agendada')}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-500 mb-1">Observações</label>
|
||||
<p className="text-gray-900 whitespace-pre-wrap">{selectedAppointment.notes || '—'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4">
|
||||
<button
|
||||
onClick={() => setSelectedAppointment(null)}
|
||||
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -61,7 +61,11 @@ const formatDoctorName = (fullName: string): string => {
|
||||
return `Dr. ${name}`;
|
||||
};
|
||||
|
||||
export function SecretaryDoctorList() {
|
||||
export function SecretaryDoctorList({
|
||||
onOpenSchedule,
|
||||
}: {
|
||||
onOpenSchedule?: (doctorId: string) => void;
|
||||
}) {
|
||||
const [doctors, setDoctors] = useState<Doctor[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
@ -79,6 +83,8 @@ export function SecretaryDoctorList() {
|
||||
crm_uf: "",
|
||||
specialty: "",
|
||||
});
|
||||
const [showViewModal, setShowViewModal] = useState(false);
|
||||
const [selectedDoctor, setSelectedDoctor] = useState<Doctor | null>(null);
|
||||
|
||||
const loadDoctors = async () => {
|
||||
setLoading(true);
|
||||
@ -378,12 +384,26 @@ export function SecretaryDoctorList() {
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedDoctor(doctor);
|
||||
setShowViewModal(true);
|
||||
}}
|
||||
title="Visualizar"
|
||||
className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
// Prefer callback from parent to switch tab; fallback to sessionStorage
|
||||
if (onOpenSchedule) {
|
||||
onOpenSchedule(doctor.id);
|
||||
} else {
|
||||
sessionStorage.setItem("selectedDoctorForSchedule", doctor.id);
|
||||
// dispatch a custom event to inform parent (optional)
|
||||
window.dispatchEvent(new CustomEvent("open-doctor-schedule"));
|
||||
}
|
||||
}}
|
||||
title="Gerenciar agenda"
|
||||
className="p-2 text-green-600 hover:bg-green-50 rounded-lg transition-colors"
|
||||
>
|
||||
@ -597,6 +617,51 @@ export function SecretaryDoctorList() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal de Visualizar Médico */}
|
||||
{showViewModal && selectedDoctor && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Visualizar Médico</h2>
|
||||
<button
|
||||
onClick={() => setShowViewModal(false)}
|
||||
className="p-2 text-gray-400 hover:text-gray-600 rounded-lg transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Nome</p>
|
||||
<p className="text-gray-900 font-medium">{selectedDoctor.full_name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Especialidade</p>
|
||||
<p className="text-gray-900">{selectedDoctor.specialty || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">CRM</p>
|
||||
<p className="text-gray-900">{selectedDoctor.crm || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Email</p>
|
||||
<p className="text-gray-900">{selectedDoctor.email || '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-200 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => setShowViewModal(false)}
|
||||
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,6 +92,15 @@ export function SecretaryDoctorSchedule() {
|
||||
loadDoctors();
|
||||
}, []);
|
||||
|
||||
// If a doctor id was requested by other components (via sessionStorage), select it
|
||||
useEffect(() => {
|
||||
const requested = sessionStorage.getItem("selectedDoctorForSchedule");
|
||||
if (requested) {
|
||||
setSelectedDoctorId(requested);
|
||||
sessionStorage.removeItem("selectedDoctorForSchedule");
|
||||
}
|
||||
}, [doctors]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("[SecretaryDoctorSchedule] Estado availabilities atualizado:", {
|
||||
count: availabilities.length,
|
||||
|
||||
@ -40,7 +40,11 @@ const buscarEnderecoViaCEP = async (cep: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export function SecretaryPatientList() {
|
||||
export function SecretaryPatientList({
|
||||
onOpenAppointment,
|
||||
}: {
|
||||
onOpenAppointment?: (patientId: string) => void;
|
||||
}) {
|
||||
const { user } = useAuth();
|
||||
const [patients, setPatients] = useState<Patient[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -54,6 +58,8 @@ export function SecretaryPatientList() {
|
||||
const [modalMode, setModalMode] = useState<"create" | "edit">("create");
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [patientToDelete, setPatientToDelete] = useState<Patient | null>(null);
|
||||
const [showViewModal, setShowViewModal] = useState(false);
|
||||
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
||||
const [formData, setFormData] = useState<PacienteFormData>({
|
||||
nome: "",
|
||||
social_name: "",
|
||||
@ -136,7 +142,15 @@ export function SecretaryPatientList() {
|
||||
return currentMonth === birthMonth;
|
||||
})();
|
||||
|
||||
return matchesSearch && matchesBirthday;
|
||||
// Filtro de convênio
|
||||
const matchesInsurance =
|
||||
insuranceFilter === "Todos" ||
|
||||
((patient as any).convenio || "Particular") === insuranceFilter;
|
||||
|
||||
// Filtro VIP (se o backend fornecer uma flag 'is_vip' ou 'vip')
|
||||
const matchesVIP = !showVIP || ((patient as any).is_vip === true || (patient as any).vip === true);
|
||||
|
||||
return matchesSearch && matchesBirthday && matchesInsurance && matchesVIP;
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
@ -333,6 +347,21 @@ export function SecretaryPatientList() {
|
||||
setShowDeleteDialog(true);
|
||||
};
|
||||
|
||||
const handleViewPatient = (patient: Patient) => {
|
||||
setSelectedPatient(patient);
|
||||
setShowViewModal(true);
|
||||
};
|
||||
|
||||
const handleSchedulePatient = (patient: Patient) => {
|
||||
if (onOpenAppointment) {
|
||||
onOpenAppointment(patient.id as string);
|
||||
} else {
|
||||
// fallback: store in sessionStorage and dispatch event
|
||||
sessionStorage.setItem("selectedPatientForAppointment", patient.id as string);
|
||||
window.dispatchEvent(new CustomEvent("open-create-appointment"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!patientToDelete?.id) return;
|
||||
|
||||
@ -518,17 +547,19 @@ export function SecretaryPatientList() {
|
||||
{/* TODO: Buscar próximo agendamento */}—
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-700">
|
||||
Particular
|
||||
{(patient as any).convenio || "Particular"}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleViewPatient(patient)}
|
||||
title="Visualizar"
|
||||
className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSchedulePatient(patient)}
|
||||
title="Agendar consulta"
|
||||
className="p-2 text-green-600 hover:bg-green-50 rounded-lg transition-colors"
|
||||
>
|
||||
@ -596,6 +627,50 @@ export function SecretaryPatientList() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal de Visualizar Paciente */}
|
||||
{showViewModal && selectedPatient && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6 border-b border-gray-200 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Visualizar Paciente</h2>
|
||||
<button
|
||||
onClick={() => setShowViewModal(false)}
|
||||
className="p-2 text-gray-400 hover:text-gray-600 rounded-lg transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Nome</p>
|
||||
<p className="text-gray-900 font-medium">{selectedPatient.full_name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Email</p>
|
||||
<p className="text-gray-900">{selectedPatient.email || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Telefone</p>
|
||||
<p className="text-gray-900">{selectedPatient.phone_mobile || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Convênio</p>
|
||||
<p className="text-gray-900">{(selectedPatient as any).convenio || 'Particular'}</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => setShowViewModal(false)}
|
||||
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
{showDeleteDialog && patientToDelete && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
|
||||
@ -35,6 +35,14 @@ export function SecretaryReportList() {
|
||||
loadPatients();
|
||||
}, []);
|
||||
|
||||
// Recarrega automaticamente quando o filtro de status muda
|
||||
// (evita depender do clique em Buscar)
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
loadReports();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [statusFilter]);
|
||||
|
||||
const loadPatients = async () => {
|
||||
try {
|
||||
const data = await patientService.list();
|
||||
@ -267,9 +275,24 @@ export function SecretaryReportList() {
|
||||
const loadReports = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await reportService.list();
|
||||
// Se um filtro de status estiver aplicado, encaminhar para o serviço
|
||||
// Cast explícito para o tipo esperado pelo serviço (ReportStatus)
|
||||
const filters = statusFilter ? { status: statusFilter as any } : undefined;
|
||||
console.log("[SecretaryReportList] loadReports filters:", filters);
|
||||
const data = await reportService.list(filters);
|
||||
console.log("✅ Relatórios carregados:", data);
|
||||
setReports(Array.isArray(data) ? data : []);
|
||||
// Garantir filtro por status no cliente como fallback caso o backend
|
||||
// não aplique corretamente o filtro. Normaliza para evitar problemas
|
||||
// de case/whitespace.
|
||||
let reportsList = Array.isArray(data) ? data : [];
|
||||
if (statusFilter) {
|
||||
const normalized = statusFilter.toString().toLowerCase().trim();
|
||||
reportsList = reportsList.filter((r) => {
|
||||
const rs = (r.status || "").toString().toLowerCase().trim();
|
||||
return rs === normalized;
|
||||
});
|
||||
}
|
||||
setReports(reportsList);
|
||||
if (Array.isArray(data) && data.length === 0) {
|
||||
console.warn("⚠️ Nenhum relatório encontrado na API");
|
||||
}
|
||||
|
||||
@ -90,8 +90,24 @@ export default function PainelSecretaria() {
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-[1400px] mx-auto px-6 py-8">
|
||||
{activeTab === "pacientes" && <SecretaryPatientList />}
|
||||
{activeTab === "medicos" && <SecretaryDoctorList />}
|
||||
{activeTab === "pacientes" && (
|
||||
<SecretaryPatientList
|
||||
onOpenAppointment={(patientId: string) => {
|
||||
// store selected patient for appointment and switch to consultas tab
|
||||
sessionStorage.setItem("selectedPatientForAppointment", patientId);
|
||||
setActiveTab("consultas");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "medicos" && (
|
||||
<SecretaryDoctorList
|
||||
onOpenSchedule={(doctorId: string) => {
|
||||
// store selected doctor for schedule and switch to agenda tab
|
||||
sessionStorage.setItem("selectedDoctorForSchedule", doctorId);
|
||||
setActiveTab("agenda");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "consultas" && <SecretaryAppointmentList />}
|
||||
{activeTab === "agenda" && <SecretaryDoctorSchedule />}
|
||||
{activeTab === "relatorios" && <SecretaryReportList />}
|
||||
|
||||
@ -27,6 +27,9 @@ class ReportService {
|
||||
params["status"] = `eq.${filters.status}`;
|
||||
}
|
||||
|
||||
// Log para depuração: mostra quais params serão enviados
|
||||
console.log("[ReportService] list() params:", params);
|
||||
|
||||
const response = await apiClient.get<Report[]>(this.basePath, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user