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 (
|
||||
<div className={`fixed bottom-6 right-6 z-50 ${className}`}>
|
||||
<div className={`fixed bottom-6 left-6 z-50 ${className}`}>
|
||||
{/* Floating Button */}
|
||||
{!isOpen && (
|
||||
<button
|
||||
|
||||
@ -8,6 +8,8 @@ import {
|
||||
type Report,
|
||||
patientService,
|
||||
type Patient,
|
||||
doctorService,
|
||||
type Doctor,
|
||||
} from "../../services";
|
||||
|
||||
export function SecretaryReportList() {
|
||||
@ -20,8 +22,10 @@ export function SecretaryReportList() {
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [selectedReport, setSelectedReport] = useState<Report | null>(null);
|
||||
const [patients, setPatients] = useState<Patient[]>([]);
|
||||
const [doctors, setDoctors] = useState<Doctor[]>([]);
|
||||
const [formData, setFormData] = useState({
|
||||
patient_id: "",
|
||||
doctor_id: "",
|
||||
exam: "",
|
||||
diagnosis: "",
|
||||
conclusion: "",
|
||||
@ -33,6 +37,7 @@ export function SecretaryReportList() {
|
||||
useEffect(() => {
|
||||
loadReports();
|
||||
loadPatients();
|
||||
loadDoctors();
|
||||
}, []);
|
||||
|
||||
// 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 = () => {
|
||||
setFormData({
|
||||
patient_id: "",
|
||||
doctor_id: "",
|
||||
exam: "",
|
||||
diagnosis: "",
|
||||
conclusion: "",
|
||||
@ -74,6 +89,7 @@ export function SecretaryReportList() {
|
||||
setSelectedReport(report);
|
||||
setFormData({
|
||||
patient_id: report.patient_id,
|
||||
doctor_id: "",
|
||||
exam: report.exam || "",
|
||||
diagnosis: report.diagnosis || "",
|
||||
conclusion: report.conclusion || "",
|
||||
@ -92,12 +108,30 @@ export function SecretaryReportList() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.doctor_id && !formData.requested_by) {
|
||||
toast.error("Selecione um médico solicitante");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await reportService.create({
|
||||
console.log("[SecretaryReportList] Criando relatório com dados:", {
|
||||
patient_id: formData.patient_id,
|
||||
exam: formData.exam,
|
||||
diagnosis: formData.diagnosis,
|
||||
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!");
|
||||
@ -556,6 +590,32 @@ export function SecretaryReportList() {
|
||||
</select>
|
||||
</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>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Exame
|
||||
@ -847,7 +907,32 @@ export function SecretaryReportList() {
|
||||
|
||||
<div>
|
||||
<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>
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@ -78,6 +78,8 @@ const AcompanhamentoPaciente: React.FC = () => {
|
||||
const [especialidadeFiltro, setEspecialidadeFiltro] = useState<string>("");
|
||||
const [laudos, setLaudos] = useState<Report[]>([]);
|
||||
const [loadingLaudos, setLoadingLaudos] = useState(false);
|
||||
const [selectedLaudo, setSelectedLaudo] = useState<Report | null>(null);
|
||||
const [showLaudoModal, setShowLaudoModal] = useState(false);
|
||||
const [paginaProximas, setPaginaProximas] = useState(1);
|
||||
const [paginaPassadas, setPaginaPassadas] = useState(1);
|
||||
const consultasPorPagina = 10;
|
||||
@ -934,7 +936,7 @@ const AcompanhamentoPaciente: React.FC = () => {
|
||||
Exame
|
||||
</th>
|
||||
<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 className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
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">
|
||||
Data
|
||||
</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>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-slate-700">
|
||||
@ -957,7 +962,7 @@ const AcompanhamentoPaciente: React.FC = () => {
|
||||
{laudo.exam || "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-300">
|
||||
{laudo.diagnosis || "-"}
|
||||
{laudo.requested_by || "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<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">
|
||||
{new Date(laudo.created_at).toLocaleDateString("pt-BR")}
|
||||
</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>
|
||||
))}
|
||||
</tbody>
|
||||
@ -990,6 +1006,146 @@ const AcompanhamentoPaciente: React.FC = () => {
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user