- Adiciona Edge Function para calcular slots disponíveis - Implementa método callFunction() no apiClient para Edge Functions - Atualiza appointmentService com getAvailableSlots() e create() - Simplifica AgendamentoConsulta removendo lógica manual de slots - Remove arquivos de teste e documentação temporária - Atualiza README com documentação completa - Adiciona AGENDAMENTO-SLOTS-API.md com detalhes da implementação - Corrige formatação de dados (telefone, CPF, nomes) - Melhora diálogos de confirmação e feedback visual - Otimiza performance e user experience
856 lines
33 KiB
TypeScript
856 lines
33 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import toast from "react-hot-toast";
|
|
import { Search, FileText, Download, Plus, Eye, Edit2, X } from "lucide-react";
|
|
import jsPDF from "jspdf";
|
|
import html2canvas from "html2canvas";
|
|
import {
|
|
reportService,
|
|
type Report,
|
|
patientService,
|
|
type Patient,
|
|
} from "../../services";
|
|
|
|
export function SecretaryReportList() {
|
|
const [reports, setReports] = useState<Report[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const [statusFilter, setStatusFilter] = useState<string>("");
|
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
const [showViewModal, setShowViewModal] = useState(false);
|
|
const [showEditModal, setShowEditModal] = useState(false);
|
|
const [selectedReport, setSelectedReport] = useState<Report | null>(null);
|
|
const [patients, setPatients] = useState<Patient[]>([]);
|
|
const [formData, setFormData] = useState({
|
|
patient_id: "",
|
|
exam: "",
|
|
diagnosis: "",
|
|
conclusion: "",
|
|
status: "draft" as "draft" | "completed" | "pending" | "cancelled",
|
|
cid_code: "",
|
|
requested_by: "",
|
|
});
|
|
|
|
useEffect(() => {
|
|
loadReports();
|
|
loadPatients();
|
|
}, []);
|
|
|
|
const loadPatients = async () => {
|
|
try {
|
|
const data = await patientService.list();
|
|
setPatients(Array.isArray(data) ? data : []);
|
|
} catch (error) {
|
|
console.error("Erro ao carregar pacientes:", error);
|
|
}
|
|
};
|
|
|
|
const handleOpenCreateModal = () => {
|
|
setFormData({
|
|
patient_id: "",
|
|
exam: "",
|
|
diagnosis: "",
|
|
conclusion: "",
|
|
status: "draft",
|
|
cid_code: "",
|
|
requested_by: "",
|
|
});
|
|
setShowCreateModal(true);
|
|
};
|
|
|
|
const handleViewReport = (report: Report) => {
|
|
setSelectedReport(report);
|
|
setShowViewModal(true);
|
|
};
|
|
|
|
const handleOpenEditModal = (report: Report) => {
|
|
setSelectedReport(report);
|
|
setFormData({
|
|
patient_id: report.patient_id,
|
|
exam: report.exam || "",
|
|
diagnosis: report.diagnosis || "",
|
|
conclusion: report.conclusion || "",
|
|
status: report.status || "draft",
|
|
cid_code: report.cid_code || "",
|
|
requested_by: report.requested_by || "",
|
|
});
|
|
setShowEditModal(true);
|
|
};
|
|
|
|
const handleCreateReport = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!formData.patient_id) {
|
|
toast.error("Selecione um paciente");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await reportService.create({
|
|
patient_id: formData.patient_id,
|
|
exam: formData.exam,
|
|
diagnosis: formData.diagnosis,
|
|
conclusion: formData.conclusion,
|
|
});
|
|
|
|
toast.success("Relatório criado com sucesso!");
|
|
setShowCreateModal(false);
|
|
loadReports();
|
|
} catch (error) {
|
|
console.error("Erro ao criar relatório:", error);
|
|
toast.error("Erro ao criar relatório");
|
|
}
|
|
};
|
|
|
|
const handleUpdateReport = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!selectedReport?.id) {
|
|
toast.error("Relatório não identificado");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await reportService.update(selectedReport.id, {
|
|
patient_id: formData.patient_id,
|
|
exam: formData.exam || undefined,
|
|
diagnosis: formData.diagnosis || undefined,
|
|
conclusion: formData.conclusion || undefined,
|
|
status: formData.status,
|
|
cid_code: formData.cid_code || undefined,
|
|
requested_by: formData.requested_by || undefined,
|
|
});
|
|
|
|
toast.success("Relatório atualizado com sucesso!");
|
|
setShowEditModal(false);
|
|
setSelectedReport(null);
|
|
loadReports();
|
|
} catch (error) {
|
|
console.error("Erro ao atualizar relatório:", error);
|
|
toast.error("Erro ao atualizar relatório");
|
|
}
|
|
};
|
|
|
|
const handleDownloadReport = async (report: Report) => {
|
|
try {
|
|
// Criar um elemento temporário para o relatório
|
|
const reportElement = document.createElement("div");
|
|
reportElement.style.padding = "40px";
|
|
reportElement.style.backgroundColor = "white";
|
|
reportElement.style.width = "800px";
|
|
reportElement.style.fontFamily = "Arial, sans-serif";
|
|
|
|
reportElement.innerHTML = `
|
|
<div style="text-align: center; margin-bottom: 30px; border-bottom: 2px solid #333; padding-bottom: 20px;">
|
|
<h1 style="color: #16a34a; margin: 0 0 10px 0; font-size: 28px;">Relatório Médico</h1>
|
|
<p style="color: #666; margin: 0; font-size: 14px;">${report.order_number || "—"}</p>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 25px;">
|
|
<div style="background-color: #f3f4f6; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
|
|
<div>
|
|
<p style="margin: 0 0 5px 0; font-size: 12px; color: #6b7280; font-weight: 600;">STATUS</p>
|
|
<p style="margin: 0; font-size: 14px; color: #111827;">${
|
|
report.status === "completed"
|
|
? "✅ Concluído"
|
|
: report.status === "pending"
|
|
? "⏳ Pendente"
|
|
: report.status === "draft"
|
|
? "📝 Rascunho"
|
|
: "❌ Cancelado"
|
|
}</p>
|
|
</div>
|
|
<div>
|
|
<p style="margin: 0 0 5px 0; font-size: 12px; color: #6b7280; font-weight: 600;">DATA</p>
|
|
<p style="margin: 0; font-size: 14px; color: #111827;">${formatDate(report.created_at)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
${report.exam ? `
|
|
<div style="margin-bottom: 20px;">
|
|
<h3 style="color: #16a34a; font-size: 16px; margin: 0 0 10px 0; border-bottom: 1px solid #e5e7eb; padding-bottom: 5px;">EXAME REALIZADO</h3>
|
|
<p style="margin: 0; color: #374151; line-height: 1.6;">${report.exam}</p>
|
|
</div>
|
|
` : ""}
|
|
|
|
${report.cid_code ? `
|
|
<div style="margin-bottom: 20px;">
|
|
<h3 style="color: #16a34a; font-size: 16px; margin: 0 0 10px 0; border-bottom: 1px solid #e5e7eb; padding-bottom: 5px;">CÓDIGO CID-10</h3>
|
|
<p style="margin: 0; color: #374151; line-height: 1.6; font-family: monospace; background: #f9fafb; padding: 8px; border-radius: 4px;">${report.cid_code}</p>
|
|
</div>
|
|
` : ""}
|
|
|
|
${report.requested_by ? `
|
|
<div style="margin-bottom: 20px;">
|
|
<h3 style="color: #16a34a; font-size: 16px; margin: 0 0 10px 0; border-bottom: 1px solid #e5e7eb; padding-bottom: 5px;">SOLICITADO POR</h3>
|
|
<p style="margin: 0; color: #374151; line-height: 1.6;">${report.requested_by}</p>
|
|
</div>
|
|
` : ""}
|
|
|
|
${report.diagnosis ? `
|
|
<div style="margin-bottom: 20px;">
|
|
<h3 style="color: #16a34a; font-size: 16px; margin: 0 0 10px 0; border-bottom: 1px solid #e5e7eb; padding-bottom: 5px;">DIAGNÓSTICO</h3>
|
|
<p style="margin: 0; color: #374151; line-height: 1.8; white-space: pre-wrap;">${report.diagnosis}</p>
|
|
</div>
|
|
` : ""}
|
|
|
|
${report.conclusion ? `
|
|
<div style="margin-bottom: 20px;">
|
|
<h3 style="color: #16a34a; font-size: 16px; margin: 0 0 10px 0; border-bottom: 1px solid #e5e7eb; padding-bottom: 5px;">CONCLUSÃO</h3>
|
|
<p style="margin: 0; color: #374151; line-height: 1.8; white-space: pre-wrap;">${report.conclusion}</p>
|
|
</div>
|
|
` : ""}
|
|
|
|
<div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center; color: #9ca3af; font-size: 12px;">
|
|
<p style="margin: 0;">Documento gerado em ${new Date().toLocaleDateString("pt-BR", { day: "2-digit", month: "long", year: "numeric" })}</p>
|
|
</div>
|
|
`;
|
|
|
|
// Adicionar ao DOM temporariamente
|
|
document.body.appendChild(reportElement);
|
|
|
|
// Capturar como imagem
|
|
const canvas = await html2canvas(reportElement, {
|
|
scale: 2,
|
|
backgroundColor: "#ffffff",
|
|
logging: false,
|
|
});
|
|
|
|
// Remover elemento temporário
|
|
document.body.removeChild(reportElement);
|
|
|
|
// Criar PDF
|
|
const imgWidth = 210; // A4 width in mm
|
|
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
const pdf = new jsPDF("p", "mm", "a4");
|
|
const imgData = canvas.toDataURL("image/png");
|
|
|
|
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
|
|
pdf.save(`relatorio-${report.order_number || "sem-numero"}.pdf`);
|
|
|
|
toast.success("Relatório baixado com sucesso!");
|
|
} catch (error) {
|
|
console.error("Erro ao gerar PDF:", error);
|
|
toast.error("Erro ao gerar PDF do relatório");
|
|
}
|
|
};
|
|
|
|
const loadReports = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await reportService.list();
|
|
console.log("✅ Relatórios carregados:", data);
|
|
setReports(Array.isArray(data) ? data : []);
|
|
if (Array.isArray(data) && data.length === 0) {
|
|
console.warn("⚠️ Nenhum relatório encontrado na API");
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ Erro ao carregar relatórios:", error);
|
|
toast.error("Erro ao carregar relatórios");
|
|
setReports([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSearch = () => {
|
|
loadReports();
|
|
};
|
|
|
|
const handleClear = () => {
|
|
setSearchTerm("");
|
|
setStatusFilter("");
|
|
loadReports();
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString("pt-BR", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
} catch {
|
|
return "—";
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">Relatórios</h1>
|
|
<p className="text-gray-600 mt-1">
|
|
Visualize e baixe relatórios do sistema
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleOpenCreateModal}
|
|
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
Novo Relatório
|
|
</button>
|
|
</div>
|
|
|
|
{/* Search and Filters */}
|
|
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
|
<div className="flex gap-3">
|
|
<div className="flex-1 relative">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
|
<input
|
|
type="text"
|
|
placeholder="Buscar relatórios..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={handleSearch}
|
|
className="px-6 py-2.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
|
|
>
|
|
Buscar
|
|
</button>
|
|
<button
|
|
onClick={handleClear}
|
|
className="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
|
>
|
|
Limpar
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-6">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-gray-600">Status:</span>
|
|
<select
|
|
value={statusFilter}
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
className="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
|
>
|
|
<option value="">Todos</option>
|
|
<option value="draft">Rascunho</option>
|
|
<option value="completed">Concluído</option>
|
|
<option value="pending">Pendente</option>
|
|
<option value="cancelled">Cancelado</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
|
Relatório
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
|
Status
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
|
Criado Em
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
|
Solicitante
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
|
Ações
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200">
|
|
{loading ? (
|
|
<tr>
|
|
<td
|
|
colSpan={5}
|
|
className="px-6 py-12 text-center text-gray-500"
|
|
>
|
|
Carregando relatórios...
|
|
</td>
|
|
</tr>
|
|
) : reports.length === 0 ? (
|
|
<tr>
|
|
<td
|
|
colSpan={5}
|
|
className="px-6 py-12 text-center text-gray-500"
|
|
>
|
|
Nenhum relatório encontrado
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
reports.map((report) => (
|
|
<tr
|
|
key={report.id}
|
|
className="hover:bg-gray-50 transition-colors"
|
|
>
|
|
<td className="px-6 py-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-blue-100 rounded-lg">
|
|
<FileText className="h-5 w-5 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-900">
|
|
{report.order_number}
|
|
</p>
|
|
<p className="text-xs text-gray-500">
|
|
{report.exam || "Sem exame"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span
|
|
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
|
report.status === "completed"
|
|
? "bg-green-100 text-green-800"
|
|
: report.status === "pending"
|
|
? "bg-yellow-100 text-yellow-800"
|
|
: report.status === "draft"
|
|
? "bg-gray-100 text-gray-800"
|
|
: "bg-red-100 text-red-800"
|
|
}`}
|
|
>
|
|
{report.status === "completed"
|
|
? "Concluído"
|
|
: report.status === "pending"
|
|
? "Pendente"
|
|
: report.status === "draft"
|
|
? "Rascunho"
|
|
: "Cancelado"}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-700">
|
|
{formatDate(report.created_at)}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-700">
|
|
{report.requested_by || "—"}
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => handleViewReport(report)}
|
|
title="Visualizar"
|
|
className="flex items-center gap-1 px-3 py-1.5 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
|
>
|
|
<Eye className="h-4 w-4" />
|
|
<span className="text-sm font-medium">Ver</span>
|
|
</button>
|
|
<button
|
|
onClick={() => handleOpenEditModal(report)}
|
|
title="Editar"
|
|
className="flex items-center gap-1 px-3 py-1.5 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors"
|
|
>
|
|
<Edit2 className="h-4 w-4" />
|
|
<span className="text-sm font-medium">Editar</span>
|
|
</button>
|
|
<button
|
|
onClick={() => handleDownloadReport(report)}
|
|
title="Baixar PDF"
|
|
disabled={report.status !== "completed"}
|
|
className={`flex items-center gap-1 px-3 py-1.5 rounded-lg transition-colors ${
|
|
report.status === "completed"
|
|
? "text-green-600 hover:bg-green-50 cursor-pointer"
|
|
: "text-gray-400 cursor-not-allowed"
|
|
}`}
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
<span className="text-sm font-medium">Baixar PDF</span>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Modal de Criar Relatório */}
|
|
{showCreateModal && (
|
|
<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">
|
|
<h2 className="text-2xl font-bold text-gray-900">
|
|
Novo Relatório
|
|
</h2>
|
|
</div>
|
|
|
|
<form onSubmit={handleCreateReport} className="p-6 space-y-4">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Paciente *
|
|
</label>
|
|
<select
|
|
value={formData.patient_id}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, patient_id: e.target.value })
|
|
}
|
|
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 paciente</option>
|
|
{patients.map((patient) => (
|
|
<option key={patient.id} value={patient.id}>
|
|
{patient.full_name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Exame
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.exam}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, exam: e.target.value })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500"
|
|
placeholder="Nome do exame realizado"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Diagnóstico
|
|
</label>
|
|
<textarea
|
|
value={formData.diagnosis}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, diagnosis: 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="Diagnóstico do paciente"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Conclusão
|
|
</label>
|
|
<textarea
|
|
value={formData.conclusion}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, conclusion: 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="Conclusão e recomendações"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3 pt-4">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowCreateModal(false)}
|
|
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
|
>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
|
|
>
|
|
Criar Relatório
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modal de Visualizar Relatório */}
|
|
{showViewModal && selectedReport && (
|
|
<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-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-2xl font-bold text-gray-900">
|
|
Visualizar Relatório
|
|
</h2>
|
|
<button
|
|
onClick={() => setShowViewModal(false)}
|
|
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">
|
|
Número do Relatório
|
|
</label>
|
|
<p className="text-gray-900 font-medium">
|
|
{selectedReport.order_number || "—"}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Status
|
|
</label>
|
|
<span
|
|
className={`inline-flex px-3 py-1 text-sm font-semibold rounded-full ${
|
|
selectedReport.status === "completed"
|
|
? "bg-green-100 text-green-800"
|
|
: selectedReport.status === "pending"
|
|
? "bg-yellow-100 text-yellow-800"
|
|
: selectedReport.status === "draft"
|
|
? "bg-gray-100 text-gray-800"
|
|
: "bg-red-100 text-red-800"
|
|
}`}
|
|
>
|
|
{selectedReport.status === "completed"
|
|
? "Concluído"
|
|
: selectedReport.status === "pending"
|
|
? "Pendente"
|
|
: selectedReport.status === "draft"
|
|
? "Rascunho"
|
|
: "Cancelado"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Exame
|
|
</label>
|
|
<p className="text-gray-900">{selectedReport.exam || "—"}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Código CID-10
|
|
</label>
|
|
<p className="text-gray-900">{selectedReport.cid_code || "—"}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Solicitado por
|
|
</label>
|
|
<p className="text-gray-900">{selectedReport.requested_by || "—"}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Diagnóstico
|
|
</label>
|
|
<p className="text-gray-900 whitespace-pre-wrap">
|
|
{selectedReport.diagnosis || "—"}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Conclusão
|
|
</label>
|
|
<p className="text-gray-900 whitespace-pre-wrap">
|
|
{selectedReport.conclusion || "—"}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Criado em
|
|
</label>
|
|
<p className="text-gray-900 text-sm">
|
|
{formatDate(selectedReport.created_at)}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-500 mb-1">
|
|
Atualizado em
|
|
</label>
|
|
<p className="text-gray-900 text-sm">
|
|
{formatDate(selectedReport.updated_at)}
|
|
</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>
|
|
<button
|
|
onClick={() => {
|
|
setShowViewModal(false);
|
|
handleOpenEditModal(selectedReport);
|
|
}}
|
|
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
|
>
|
|
Editar Relatório
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modal de Editar Relatório */}
|
|
{showEditModal && selectedReport && (
|
|
<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-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-2xl font-bold text-gray-900">
|
|
Editar Relatório
|
|
</h2>
|
|
<button
|
|
onClick={() => {
|
|
setShowEditModal(false);
|
|
setSelectedReport(null);
|
|
}}
|
|
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
|
>
|
|
<X className="h-5 w-5 text-gray-500" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleUpdateReport} className="p-6 space-y-4">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Número do Relatório
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={selectedReport.order_number || ""}
|
|
disabled
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-50 text-gray-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Status *
|
|
</label>
|
|
<select
|
|
value={formData.status}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, status: e.target.value as "draft" | "completed" | "pending" | "cancelled" })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
|
required
|
|
>
|
|
<option value="draft">Rascunho</option>
|
|
<option value="completed">Concluído</option>
|
|
<option value="pending">Pendente</option>
|
|
<option value="cancelled">Cancelado</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Exame
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.exam}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, exam: e.target.value })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
|
placeholder="Nome do exame realizado"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Código CID-10
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.cid_code}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, cid_code: e.target.value })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
|
placeholder="Ex: A00.0"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Solicitado por
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.requested_by}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, requested_by: e.target.value })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
|
placeholder="Nome do médico solicitante"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Diagnóstico
|
|
</label>
|
|
<textarea
|
|
value={formData.diagnosis}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, diagnosis: e.target.value })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 h-32"
|
|
placeholder="Diagnóstico do paciente"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Conclusão
|
|
</label>
|
|
<textarea
|
|
value={formData.conclusion}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, conclusion: e.target.value })
|
|
}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 h-32"
|
|
placeholder="Conclusão e recomendações"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setShowEditModal(false);
|
|
setSelectedReport(null);
|
|
}}
|
|
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
|
>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
|
>
|
|
Salvar Alterações
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|