fix: relatorios/laudos, horario de consultas do painel da secretaria, botao chatbot
This commit is contained in:
parent
398b731dd9
commit
81562e0737
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user