Vendaweb-portal/components/StimulsoftViewer.tsx

335 lines
11 KiB
TypeScript
Raw Normal View History

2026-01-08 12:09:16 +00:00
import React, { useEffect, useRef, useState } from "react";
interface StimulsoftViewerProps {
requestUrl: string;
action?: string;
width?: string;
height?: string;
onClose?: () => void;
orderId?: number;
model?: string;
}
/**
* StimulsoftViewer Component
* Componente para renderizar o Stimulsoft Reports Viewer
* Baseado no componente stimulsoft-viewer-angular do portal Angular
*
* O componente usa a biblioteca Stimulsoft Reports.JS para processar o JSON
* retornado pelo servidor e renderizar o viewer
*/
const StimulsoftViewer: React.FC<StimulsoftViewerProps> = ({
requestUrl,
action,
width = "100%",
height = "800px",
onClose,
orderId,
model,
}) => {
const viewerRef = useRef<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const formRef = useRef<HTMLFormElement | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Gerar ID único para o viewer (deve ser declarado antes do useEffect)
const viewerIdRef = useRef(
`stimulsoft-viewer-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
);
const viewerId = viewerIdRef.current;
// Construir a URL base (sem query params, pois vamos usar POST)
const baseUrl = action ? requestUrl.replace("{action}", action) : requestUrl;
const urlWithoutParams = baseUrl.split("?")[0];
useEffect(() => {
let isMounted = true;
if (!viewerRef.current) return;
// Limpar conteúdo anterior
viewerRef.current.innerHTML = "";
// Extrair parâmetros da URL original se não foram fornecidos via props
const urlParams = new URLSearchParams(baseUrl.split("?")[1] || "");
const finalOrderId = orderId
? String(orderId)
: urlParams.get("orderId") || "";
const finalModel = model || urlParams.get("model") || "A";
// Criar iframe
const iframe = document.createElement("iframe");
iframe.name = `stimulsoft-viewer-iframe-${Date.now()}`;
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "none";
iframe.style.borderRadius = "0 0 1.5rem 1.5rem";
iframe.title = "Stimulsoft Viewer";
iframe.allow = "fullscreen";
// Criar formulário que faz POST com multipart/form-data
// O componente Angular stimulsoft-viewer-angular faz POST com esses parâmetros
// O navegador vai criar o boundary automaticamente quando usamos multipart/form-data
const form = document.createElement("form");
form.method = "POST";
form.action = urlWithoutParams;
form.target = iframe.name;
form.setAttribute("enctype", "multipart/form-data");
form.style.display = "none";
// Adicionar campos do formulário conforme a requisição funcional do Angular
const fields = {
orderId: finalOrderId,
model: finalModel,
stiweb_component: "Viewer",
stiweb_imagesScalingFactor: "1",
stiweb_action: "AngularViewerData",
};
// Log para debug
console.log("StimulsoftViewer - URL:", urlWithoutParams);
console.log("StimulsoftViewer - Campos:", fields);
Object.entries(fields).forEach(([key, value]) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = String(value);
form.appendChild(input);
});
// Verificar se orderId está vazio (pode causar erro 500)
if (!finalOrderId) {
setError("Erro: orderId não foi fornecido.");
setLoading(false);
return;
}
let loadTimeout: NodeJS.Timeout;
let errorDetected = false;
const handleIframeLoad = () => {
if (isMounted && !errorDetected) {
// Verificar se houve erro no iframe (status 500, etc)
try {
const iframeDoc =
iframe.contentDocument || iframe.contentWindow?.document;
if (iframeDoc) {
const bodyText =
iframeDoc.body?.innerText || iframeDoc.body?.textContent || "";
// Se o conteúdo contém indicadores de erro
if (
bodyText.includes("500") ||
bodyText.includes("Internal Server Error") ||
bodyText.includes("Error")
) {
errorDetected = true;
setError(
"Erro ao carregar o relatório. O servidor retornou um erro."
);
setLoading(false);
return;
}
}
} catch (e) {
// CORS pode impedir acesso ao conteúdo do iframe, mas isso é normal
console.log(
"Não foi possível verificar conteúdo do iframe (pode ser CORS):",
e
);
}
clearTimeout(loadTimeout);
setLoading(false);
}
};
const handleIframeError = () => {
if (isMounted) {
errorDetected = true;
setError(
"Erro ao carregar o relatório. Verifique a URL e a conexão com o servidor."
);
setLoading(false);
}
};
// Timeout para detectar se o iframe não carregou
loadTimeout = setTimeout(() => {
if (isMounted && loading) {
// Verificar se o iframe realmente carregou
try {
if (iframe.contentWindow && iframe.contentDocument) {
// Iframe carregou, mas pode ter demorado
handleIframeLoad();
} else {
errorDetected = true;
setError("Timeout ao carregar o relatório. Verifique a conexão.");
setLoading(false);
}
} catch (e) {
// CORS pode impedir acesso, mas isso não significa erro
// Se passou muito tempo, pode ser um problema
errorDetected = true;
setError("Timeout ao carregar o relatório.");
setLoading(false);
}
}
}, 30000); // 30 segundos de timeout
iframe.addEventListener("load", handleIframeLoad);
iframe.addEventListener("error", handleIframeError);
// Adicionar iframe primeiro ao DOM e aguardar estar pronto
if (viewerRef.current) {
viewerRef.current.appendChild(iframe);
}
formRef.current = form;
iframeRef.current = iframe;
// Aguardar o iframe estar completamente carregado antes de submeter o formulário
const waitForIframeReady = () => {
if (!isMounted) return;
// Verificar se o iframe está pronto
try {
// Tentar acessar o contentWindow para verificar se está pronto
if (iframe.contentWindow) {
// Iframe está pronto, adicionar formulário e submeter
// Adicionar formulário ao body do documento
document.body.appendChild(form);
// Aguardar um pequeno delay antes de submeter
setTimeout(() => {
if (isMounted && formRef.current) {
try {
console.log("Submetendo formulário para:", urlWithoutParams);
formRef.current.submit();
} catch (e) {
console.error("Erro ao submeter formulário:", e);
errorDetected = true;
setError("Erro ao enviar requisição ao servidor.");
setLoading(false);
}
}
}, 50);
} else {
// Iframe ainda não está pronto, tentar novamente
setTimeout(waitForIframeReady, 10);
}
} catch (e) {
// Erro ao acessar contentWindow (pode ser CORS), mas continuar mesmo assim
document.body.appendChild(form);
setTimeout(() => {
if (isMounted && formRef.current) {
try {
console.log("Submetendo formulário para:", urlWithoutParams);
formRef.current.submit();
} catch (err) {
console.error("Erro ao submeter formulário:", err);
errorDetected = true;
setError("Erro ao enviar requisição ao servidor.");
setLoading(false);
}
}
}, 100);
}
};
// Aguardar um pouco antes de verificar se o iframe está pronto
setTimeout(waitForIframeReady, 50);
return () => {
isMounted = false;
clearTimeout(loadTimeout);
if (iframeRef.current) {
iframeRef.current.removeEventListener("load", handleIframeLoad);
iframeRef.current.removeEventListener("error", handleIframeError);
}
// Remover formulário do body se ainda estiver lá
if (formRef.current && formRef.current.parentNode) {
formRef.current.parentNode.removeChild(formRef.current);
}
};
}, [baseUrl, urlWithoutParams, orderId, model]);
return (
<div className="relative w-full h-full" style={{ width, height }}>
{/* <iframe
// src="http://10.1.1.205:8068/Viewer/InitViewerOrder?orderId=519003838&model=A"
src="http://10.1.1.205:8068/Viewer/ViewerEvent?orderId=519003838&model=A"
width="100%"
height="800"
style={{ border: "none" }}
/> */}
{onClose && (
<button
onClick={onClose}
className="absolute top-2 right-2 z-50 p-2 bg-white rounded-lg shadow-md hover:bg-slate-100 transition-colors"
title="Fechar"
>
<svg
className="w-5 h-5 text-slate-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
)}
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-white/80 z-40">
<div className="flex flex-col items-center gap-3">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#002147]"></div>
<p className="text-sm text-slate-600 font-medium">
Carregando relatório...
</p>
</div>
</div>
)}
{error && (
<div className="absolute inset-0 flex items-center justify-center bg-white z-40">
<div className="text-center p-6">
<p className="text-red-600 font-medium mb-2">{error}</p>
<p className="text-sm text-slate-500">URL: {baseUrl}</p>
<button
onClick={() => {
setError(null);
setLoading(true);
// Recarregar o viewer
window.location.reload();
}}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
Tentar novamente
</button>
</div>
</div>
)}
<div
id={viewerId}
ref={viewerRef}
style={{
width: "100%",
height: "100%",
border: "none",
borderRadius: "0 0 1.5rem 1.5rem",
}}
/>
</div>
);
};
export default StimulsoftViewer;