fix: relatorios/laudos, horario de consultas do painel da secretaria, botao chatbot

This commit is contained in:
Pedro Araujo da Silveira 2025-11-05 17:32:32 -03:00
parent 398b731dd9
commit 81562e0737
3 changed files with 246 additions and 5 deletions

View File

@ -132,7 +132,7 @@ const Chatbot: React.FC<ChatbotProps> = ({ className = "" }) => {
}; };
return ( return (
<div className={`fixed bottom-6 right-6 z-50 ${className}`}> <div className={`fixed bottom-6 left-6 z-50 ${className}`}>
{/* Floating Button */} {/* Floating Button */}
{!isOpen && ( {!isOpen && (
<button <button

View File

@ -8,6 +8,8 @@ import {
type Report, type Report,
patientService, patientService,
type Patient, type Patient,
doctorService,
type Doctor,
} from "../../services"; } from "../../services";
export function SecretaryReportList() { export function SecretaryReportList() {
@ -20,8 +22,10 @@ export function SecretaryReportList() {
const [showEditModal, setShowEditModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false);
const [selectedReport, setSelectedReport] = useState<Report | null>(null); const [selectedReport, setSelectedReport] = useState<Report | null>(null);
const [patients, setPatients] = useState<Patient[]>([]); const [patients, setPatients] = useState<Patient[]>([]);
const [doctors, setDoctors] = useState<Doctor[]>([]);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
patient_id: "", patient_id: "",
doctor_id: "",
exam: "", exam: "",
diagnosis: "", diagnosis: "",
conclusion: "", conclusion: "",
@ -33,6 +37,7 @@ export function SecretaryReportList() {
useEffect(() => { useEffect(() => {
loadReports(); loadReports();
loadPatients(); loadPatients();
loadDoctors();
}, []); }, []);
// Recarrega automaticamente quando o filtro de status muda // Recarrega automaticamente quando o filtro de status muda
@ -52,9 +57,19 @@ export function SecretaryReportList() {
} }
}; };
const loadDoctors = async () => {
try {
const data = await doctorService.list();
setDoctors(Array.isArray(data) ? data : []);
} catch (error) {
console.error("Erro ao carregar médicos:", error);
}
};
const handleOpenCreateModal = () => { const handleOpenCreateModal = () => {
setFormData({ setFormData({
patient_id: "", patient_id: "",
doctor_id: "",
exam: "", exam: "",
diagnosis: "", diagnosis: "",
conclusion: "", conclusion: "",
@ -74,6 +89,7 @@ export function SecretaryReportList() {
setSelectedReport(report); setSelectedReport(report);
setFormData({ setFormData({
patient_id: report.patient_id, patient_id: report.patient_id,
doctor_id: "",
exam: report.exam || "", exam: report.exam || "",
diagnosis: report.diagnosis || "", diagnosis: report.diagnosis || "",
conclusion: report.conclusion || "", conclusion: report.conclusion || "",
@ -92,12 +108,30 @@ export function SecretaryReportList() {
return; return;
} }
if (!formData.doctor_id && !formData.requested_by) {
toast.error("Selecione um médico solicitante");
return;
}
try { try {
await reportService.create({ console.log("[SecretaryReportList] Criando relatório com dados:", {
patient_id: formData.patient_id, patient_id: formData.patient_id,
exam: formData.exam, exam: formData.exam,
diagnosis: formData.diagnosis, diagnosis: formData.diagnosis,
conclusion: formData.conclusion, conclusion: formData.conclusion,
cid_code: formData.cid_code,
requested_by: formData.requested_by,
status: formData.status,
});
await reportService.create({
patient_id: formData.patient_id,
exam: formData.exam || undefined,
diagnosis: formData.diagnosis || undefined,
conclusion: formData.conclusion || undefined,
cid_code: formData.cid_code || undefined,
requested_by: formData.requested_by || undefined,
status: formData.status,
}); });
toast.success("Relatório criado com sucesso!"); toast.success("Relatório criado com sucesso!");
@ -556,6 +590,32 @@ export function SecretaryReportList() {
</select> </select>
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Médico Solicitante *
</label>
<select
value={formData.doctor_id}
onChange={(e) => {
const selectedDoctor = doctors.find(d => d.id === e.target.value);
setFormData({
...formData,
doctor_id: e.target.value,
requested_by: selectedDoctor ? `Dr. ${selectedDoctor.full_name}` : ""
});
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500"
required
>
<option value="">Selecione um médico</option>
{doctors.map((doctor) => (
<option key={doctor.id} value={doctor.id}>
Dr. {doctor.full_name} - {doctor.specialty}
</option>
))}
</select>
</div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
Exame Exame
@ -847,7 +907,32 @@ export function SecretaryReportList() {
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
Solicitado por Médico Solicitante
</label>
<select
value={formData.doctor_id}
onChange={(e) => {
const selectedDoctor = doctors.find(d => d.id === e.target.value);
setFormData({
...formData,
doctor_id: e.target.value,
requested_by: selectedDoctor ? `Dr. ${selectedDoctor.full_name}` : ""
});
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
>
<option value="">Selecione um médico</option>
{doctors.map((doctor) => (
<option key={doctor.id} value={doctor.id}>
Dr. {doctor.full_name} - {doctor.specialty}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Solicitado por (texto livre)
</label> </label>
<input <input
type="text" type="text"

View File

@ -78,6 +78,8 @@ const AcompanhamentoPaciente: React.FC = () => {
const [especialidadeFiltro, setEspecialidadeFiltro] = useState<string>(""); const [especialidadeFiltro, setEspecialidadeFiltro] = useState<string>("");
const [laudos, setLaudos] = useState<Report[]>([]); const [laudos, setLaudos] = useState<Report[]>([]);
const [loadingLaudos, setLoadingLaudos] = useState(false); const [loadingLaudos, setLoadingLaudos] = useState(false);
const [selectedLaudo, setSelectedLaudo] = useState<Report | null>(null);
const [showLaudoModal, setShowLaudoModal] = useState(false);
const [paginaProximas, setPaginaProximas] = useState(1); const [paginaProximas, setPaginaProximas] = useState(1);
const [paginaPassadas, setPaginaPassadas] = useState(1); const [paginaPassadas, setPaginaPassadas] = useState(1);
const consultasPorPagina = 10; const consultasPorPagina = 10;
@ -934,7 +936,7 @@ const AcompanhamentoPaciente: React.FC = () => {
Exame Exame
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Diagnóstico Médico Solicitante
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Status Status
@ -942,6 +944,9 @@ const AcompanhamentoPaciente: React.FC = () => {
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Data Data
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Ações
</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200 dark:divide-slate-700"> <tbody className="divide-y divide-gray-200 dark:divide-slate-700">
@ -957,7 +962,7 @@ const AcompanhamentoPaciente: React.FC = () => {
{laudo.exam || "-"} {laudo.exam || "-"}
</td> </td>
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-300"> <td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-300">
{laudo.diagnosis || "-"} {laudo.requested_by || "-"}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<span <span
@ -983,6 +988,17 @@ const AcompanhamentoPaciente: React.FC = () => {
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-300"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-300">
{new Date(laudo.created_at).toLocaleDateString("pt-BR")} {new Date(laudo.created_at).toLocaleDateString("pt-BR")}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm">
<button
onClick={() => {
setSelectedLaudo(laudo);
setShowLaudoModal(true);
}}
className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium"
>
Ver Detalhes
</button>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@ -990,6 +1006,146 @@ const AcompanhamentoPaciente: React.FC = () => {
</div> </div>
)} )}
</div> </div>
{/* Modal de Detalhes do Laudo */}
{showLaudoModal && selectedLaudo && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6 border-b border-gray-200 dark:border-slate-700 flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
Detalhes do Laudo
</h2>
<button
onClick={() => {
setShowLaudoModal(false);
setSelectedLaudo(null);
}}
className="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors"
>
<XCircle className="h-6 w-6 text-gray-500 dark:text-gray-400" />
</button>
</div>
<div className="p-6 space-y-6">
{/* Informações Básicas */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
Número do Laudo
</label>
<p className="text-gray-900 dark:text-white font-medium">
{selectedLaudo.order_number}
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
Data de Criação
</label>
<p className="text-gray-900 dark:text-white font-medium">
{new Date(selectedLaudo.created_at).toLocaleDateString("pt-BR", {
day: "2-digit",
month: "long",
year: "numeric"
})}
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
Status
</label>
<span
className={`inline-flex px-3 py-1 text-xs font-semibold rounded-full ${
selectedLaudo.status === "completed"
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
: selectedLaudo.status === "pending"
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"
: selectedLaudo.status === "cancelled"
? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300"
}`}
>
{selectedLaudo.status === "completed"
? "Concluído"
: selectedLaudo.status === "pending"
? "Pendente"
: selectedLaudo.status === "cancelled"
? "Cancelado"
: "Rascunho"}
</span>
</div>
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
Médico Solicitante
</label>
<p className="text-gray-900 dark:text-white font-medium">
{selectedLaudo.requested_by || "-"}
</p>
</div>
</div>
{/* Exame */}
{selectedLaudo.exam && (
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Exame Realizado
</label>
<p className="text-gray-900 dark:text-white bg-gray-50 dark:bg-slate-700 p-4 rounded-lg">
{selectedLaudo.exam}
</p>
</div>
)}
{/* Código CID */}
{selectedLaudo.cid_code && (
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Código CID-10
</label>
<p className="text-gray-900 dark:text-white bg-gray-50 dark:bg-slate-700 p-4 rounded-lg font-mono">
{selectedLaudo.cid_code}
</p>
</div>
)}
{/* Diagnóstico */}
{selectedLaudo.diagnosis && (
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Diagnóstico
</label>
<p className="text-gray-900 dark:text-white bg-gray-50 dark:bg-slate-700 p-4 rounded-lg whitespace-pre-wrap">
{selectedLaudo.diagnosis}
</p>
</div>
)}
{/* Conclusão */}
{selectedLaudo.conclusion && (
<div>
<label className="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Conclusão
</label>
<p className="text-gray-900 dark:text-white bg-gray-50 dark:bg-slate-700 p-4 rounded-lg whitespace-pre-wrap">
{selectedLaudo.conclusion}
</p>
</div>
)}
</div>
<div className="p-6 border-t border-gray-200 dark:border-slate-700 flex justify-end">
<button
onClick={() => {
setShowLaudoModal(false);
setSelectedLaudo(null);
}}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Fechar
</button>
</div>
</div>
</div>
)}
</div> </div>
); );