603 lines
22 KiB
TypeScript
603 lines
22 KiB
TypeScript
/**
|
||
* 文件预览组件
|
||
* 显示文档内容和评查点高亮
|
||
*/
|
||
import { useState, useEffect, useRef, ChangeEvent } from 'react';
|
||
import { Document, Page, pdfjs } from 'react-pdf';
|
||
import { DOCUMENT_URL } from '~/api/axios-client';
|
||
|
||
// 设置worker路径为public目录下的worker文件
|
||
// 使用已经下载的兼容版本 (pdfjs-dist v2.12.313)
|
||
// 2025/09/28 使用新版本的pdfjs-dist v4.8.69
|
||
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||
|
||
// 导入统一的ReviewPoint类型
|
||
import { type ReviewPoint } from './';
|
||
import { toastService } from '../ui/Toast';
|
||
|
||
/**
|
||
* 自定义样式
|
||
* 这些样式解决了PDF页面在放大时互相重叠的问题
|
||
*/
|
||
const styles = {
|
||
pdfContainer: {
|
||
display: 'flex',
|
||
flexDirection: 'column' as const,
|
||
alignItems: 'center',
|
||
width: '100%',
|
||
position: 'relative' as const,
|
||
},
|
||
pageContainer: {
|
||
display: 'flex',
|
||
flexDirection: 'column' as const,
|
||
alignItems: 'center',
|
||
width: '100%',
|
||
position: 'relative' as const,
|
||
}
|
||
};
|
||
|
||
// 定义文档内容类型
|
||
interface FileContent {
|
||
title: string;
|
||
contractNumber: string;
|
||
path: string;
|
||
ocrResult?: {
|
||
__meta?: {
|
||
page_offset?: number;
|
||
};
|
||
}; // 添加ocrResult属性
|
||
parties: {
|
||
partyA: {
|
||
name: string;
|
||
address: string;
|
||
representative: string;
|
||
phone: string;
|
||
};
|
||
partyB: {
|
||
name: string;
|
||
address: string;
|
||
representative: string;
|
||
phone: string;
|
||
};
|
||
};
|
||
sections: {
|
||
title: string;
|
||
content: string;
|
||
}[];
|
||
template_contract_path?: string;
|
||
}
|
||
|
||
interface FilePreviewProps {
|
||
fileContent: FileContent;
|
||
reviewPoints?: ReviewPoint[]; // 设为可选
|
||
activeReviewPointResultId: string | null;
|
||
targetPage?: number; // 新增目标页码参数
|
||
isStructuredView?: boolean; // 是否显示结构化视图
|
||
}
|
||
|
||
// export function FilePreview({ fileContent, reviewPoints, activeReviewPointResultId, targetPage }: FilePreviewProps) {
|
||
export function FilePreview({ fileContent, activeReviewPointResultId, targetPage, isStructuredView = false }: FilePreviewProps) {
|
||
const [zoomLevel, setZoomLevel] = useState(100);
|
||
// const [highlightsVisible, setHighlightsVisible] = useState(true);
|
||
const contentRef = useRef<HTMLDivElement>(null);
|
||
const [numPages, setNumPages] = useState<number | null>(null);
|
||
const [loadError, setLoadError] = useState<string | null>(null);
|
||
const [pageInputValue, setPageInputValue] = useState<string>('');
|
||
|
||
// 拖拽状态管理
|
||
const [dragMode, setDragMode] = useState(false); // 是否处于拖拽模式
|
||
const [isDragging, setIsDragging] = useState(false);
|
||
const [dragCursor, setDragCursor] = useState('default');
|
||
const lastMousePosRef = useRef({ x: 0, y: 0 });
|
||
|
||
// 放大文档
|
||
const handleZoomIn = () => {
|
||
if (zoomLevel < 200) {
|
||
setZoomLevel(prevZoom => prevZoom + 10);
|
||
}
|
||
};
|
||
|
||
// 缩小文档
|
||
const handleZoomOut = () => {
|
||
if (zoomLevel > 50) {
|
||
setZoomLevel(prevZoom => prevZoom - 10);
|
||
}
|
||
};
|
||
|
||
// 切换拖拽模式
|
||
const toggleDragMode = () => {
|
||
setDragMode(prev => !prev);
|
||
setDragCursor(prev => prev === 'default' ? 'grab' : 'default');
|
||
setIsDragging(false);
|
||
};
|
||
|
||
// 处理拖拽开始
|
||
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||
if (!dragMode || e.button !== 0) return; // 只在拖拽模式下响应左键点击
|
||
|
||
// 防止选中文本
|
||
e.preventDefault();
|
||
|
||
// 设置拖拽状态
|
||
setIsDragging(true);
|
||
setDragCursor('grabbing');
|
||
|
||
// 记录鼠标初始位置
|
||
lastMousePosRef.current = {
|
||
x: e.clientX,
|
||
y: e.clientY
|
||
};
|
||
};
|
||
|
||
// 处理拖拽过程
|
||
const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||
if (!dragMode || !isDragging || !contentRef.current) return;
|
||
|
||
// 计算鼠标移动距离
|
||
const dx = e.clientX - lastMousePosRef.current.x;
|
||
const dy = e.clientY - lastMousePosRef.current.y;
|
||
|
||
// 更新容器滚动位置
|
||
contentRef.current.scrollLeft -= dx;
|
||
contentRef.current.scrollTop -= dy;
|
||
|
||
// 更新鼠标位置记录
|
||
lastMousePosRef.current = {
|
||
x: e.clientX,
|
||
y: e.clientY
|
||
};
|
||
};
|
||
|
||
// 处理拖拽结束
|
||
const handleMouseUp = () => {
|
||
if (!dragMode) return;
|
||
|
||
setIsDragging(false);
|
||
setDragCursor('grab');
|
||
};
|
||
|
||
// 监听鼠标离开窗口事件
|
||
useEffect(() => {
|
||
const handleMouseLeave = () => {
|
||
if (dragMode && isDragging) {
|
||
setIsDragging(false);
|
||
setDragCursor('grab');
|
||
}
|
||
};
|
||
|
||
document.addEventListener('mouseleave', handleMouseLeave);
|
||
document.addEventListener('mouseup', handleMouseUp as EventListener);
|
||
|
||
return () => {
|
||
document.removeEventListener('mouseleave', handleMouseLeave);
|
||
document.removeEventListener('mouseup', handleMouseUp as EventListener);
|
||
};
|
||
}, [isDragging, dragMode]);
|
||
|
||
// 处理页面跳转
|
||
const prevTargetPageRef = useRef<number | undefined>(undefined);
|
||
useEffect(() => {
|
||
// 调试信息:记录组件状态
|
||
// console.log(`FilePreview更新 - isStructuredView:${isStructuredView}, targetPage:${targetPage}, activeReviewPointResultId:${activeReviewPointResultId}, numPages:${numPages}`);
|
||
|
||
// 如果有目标页码,并且与上次相同,提示用户
|
||
if(targetPage && numPages && targetPage <= numPages && targetPage === prevTargetPageRef.current){
|
||
// toastService.success(`已跳转至目标页码`);
|
||
}
|
||
// 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转
|
||
if (targetPage && numPages && targetPage <= numPages) {
|
||
// if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) {
|
||
prevTargetPageRef.current = targetPage;
|
||
let newTargetPage = targetPage;
|
||
|
||
// 页码偏移量
|
||
try {
|
||
// 安全地访问ocrResult
|
||
if (fileContent.ocrResult && fileContent.ocrResult.__meta && fileContent.ocrResult.__meta.page_offset) {
|
||
// 可以根据需要使用page_offset调整目标页面
|
||
newTargetPage = targetPage + fileContent.ocrResult.__meta.page_offset;
|
||
}
|
||
} catch (error) {
|
||
console.error("访问ocrResult时出错:", error);
|
||
toastService.error("访问ocrResult时出错:" + (error instanceof Error ? error.message : '未知错误'));
|
||
}
|
||
|
||
const pageElementId = `page-${newTargetPage}${isStructuredView ? '-structured' : ''}`;
|
||
// console.log(`尝试跳转到元素ID: ${pageElementId}`);
|
||
|
||
const pageElement = document.getElementById(pageElementId);
|
||
if (pageElement) {
|
||
// console.log(`跳转到第${newTargetPage}页,对应评查点结果ID: ${activeReviewPointResultId}`);
|
||
pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
} else {
|
||
console.warn(`未找到页面元素: ${pageElementId}`);
|
||
}
|
||
}
|
||
}, [targetPage, numPages, fileContent, activeReviewPointResultId, isStructuredView]);
|
||
|
||
// 获取评查点对应的样式类
|
||
// const getHighlightClass = (status: string) => {
|
||
// switch (status) {
|
||
// case 'warning':
|
||
// return 'warning';
|
||
// case 'error':
|
||
// return 'error';
|
||
// case 'success':
|
||
// return 'success';
|
||
// default:
|
||
// return 'warning';
|
||
// }
|
||
// };
|
||
|
||
// 处理页码输入变化
|
||
const handlePageInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||
// 只允许输入数字
|
||
const value = e.target.value.replace(/\D/g, '');
|
||
setPageInputValue(value);
|
||
};
|
||
|
||
// 处理页码跳转
|
||
const handlePageJump = () => {
|
||
if (!pageInputValue || !numPages) return;
|
||
|
||
const targetPageNum = parseInt(pageInputValue, 10);
|
||
|
||
// 验证页码是否在有效范围内
|
||
if (targetPageNum > 0 && targetPageNum <= numPages) {
|
||
// 找到目标页面元素并滚动到该位置
|
||
const pageElement = document.getElementById(`page-${targetPageNum}`);
|
||
if (pageElement) {
|
||
pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
} else {
|
||
// 页码超出范围,显示错误信息或重置输入
|
||
toastService.warning(`请输入有效页码 (1-${numPages})`);
|
||
setPageInputValue('');
|
||
}
|
||
};
|
||
|
||
// 处理回车键跳转
|
||
const handlePageInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||
if (e.key === 'Enter') {
|
||
handlePageJump();
|
||
}
|
||
};
|
||
|
||
// PDF文档加载成功回调函数
|
||
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
|
||
setNumPages(numPages);
|
||
// console.log("PDF加载成功,页数:", numPages);
|
||
}
|
||
|
||
// 计算页面在缩放后的实际间距
|
||
const calculatePageMargin = (zoomFactor: number) => {
|
||
// 基础间距为30px,随着缩放倍数线性增加
|
||
const baseMargin = 30;
|
||
// 页面缩放后,需要额外添加的间距 = (缩放倍数 - 1) * 页面高度
|
||
const additionalMargin = Math.max(0, (zoomFactor - 1) * 800); // 800是估计的页面高度
|
||
return baseMargin + additionalMargin;
|
||
};
|
||
|
||
// 滚动到顶部
|
||
const handleScrollToTop = () => {
|
||
if (contentRef.current) {
|
||
contentRef.current.scrollTo({ top: 0, behavior: 'smooth' });
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 渲染PDF文档的所有页面
|
||
*
|
||
* 功能描述:
|
||
* 1. 生成PDF所有页面的渲染数组,每个页面包含页码标识和实际页面内容
|
||
* 2. 处理页面缩放,通过CSS transform实现页面大小调整
|
||
* 3. 在每个页面上标记对应的评查点高亮区域
|
||
* 4. 处理评查点的激活状态,显示特殊的高亮效果
|
||
*
|
||
* @returns {JSX.Element[] | null} 返回所有页面组件的数组,如果没有页数信息则返回null
|
||
*/
|
||
const renderAllPages = () => {
|
||
// 如果还没有获取到PDF总页数,返回null
|
||
if (!numPages) return null;
|
||
|
||
// 用于存储所有页面组件的数组
|
||
const pages = [];
|
||
|
||
// 遍历每一页,生成对应的页面组件
|
||
for (let i = 1; i <= numPages; i++) {
|
||
// 计算当前缩放级别下的页面容器样式
|
||
const zoomFactor = zoomLevel / 100;
|
||
const pageContainerStyle = {
|
||
...styles.pageContainer,
|
||
marginBottom: `${calculatePageMargin(zoomFactor)}px`, // 动态计算页面间距
|
||
};
|
||
|
||
// 为结构化视图和普通视图创建不同的ID
|
||
const pageId = isStructuredView ? `page-${i}-structured` : `page-${i}`;
|
||
|
||
// 为每一页创建组件
|
||
pages.push(
|
||
<div key={i} id={pageId} style={pageContainerStyle}>
|
||
{/* 页码标识,显示在页面上方 */}
|
||
<div className="text-center text-gray-500 text-sm mb-2">第 {i} 页</div>
|
||
|
||
{/* 页面容器,应用缩放变换,设置相对定位用于放置评查点高亮 */}
|
||
<div
|
||
className="page-wrapper"
|
||
style={{
|
||
// transform: `scale(${zoomFactor})`, // 根据zoomLevel应用缩放
|
||
// transformOrigin: 'top center', // 缩放原点设置为顶部中心
|
||
position: 'relative', // 相对定位,作为评查点高亮的定位参考
|
||
display: 'inline-block', // 内联块级元素,宽度由内容决定
|
||
margin: '0 auto', // 水平居中
|
||
}}
|
||
>
|
||
{/* 渲染PDF页面组件 */}
|
||
<Page
|
||
pageNumber={i} // 当前页码
|
||
scale={zoomLevel / 100}
|
||
devicePixelRatio={window.devicePixelRatio || 1} //根据设备像素比渲染
|
||
renderTextLayer={false} // 停用文本层,使文本可选择
|
||
renderAnnotationLayer={false} // 停用注释层,显示PDF内置注释
|
||
className="border border-gray-300 shadow-md" // 添加边框和阴影样式
|
||
/>
|
||
|
||
{/* 渲染评查点高亮区域 */}
|
||
{/* {highlightsVisible && pageReviewPoints.map(point => {
|
||
// 判断当前评查点是否为激活状态(被选中)
|
||
const isActive = point.id === activeReviewPointId;
|
||
|
||
return (
|
||
// 评查点高亮区域
|
||
<div
|
||
key={point.id}
|
||
// 添加多个类名:基础高亮区域、PDF专用高亮、状态样式类、激活状态类
|
||
className={`highlight-area pdf-highlight ${getHighlightClass(point.status)} ${isActive ? 'highlight-focus' : ''}`}
|
||
// 设置唯一标识,用于滚动定位和DOM查询
|
||
data-review-id={point.id}
|
||
// 设置高亮区域的样式
|
||
style={{
|
||
position: 'absolute', // 绝对定位,相对于页面容器
|
||
zIndex: isActive ? 20 : 10, // 激活状态时提高层级,确保在最上层显示
|
||
boxShadow: isActive ? '0 0 0 2px yellow, 0 0 10px rgba(0,0,0,0.3)' : '', // 激活时添加特殊阴影效果
|
||
// 根据评查点的位置信息计算高亮区域位置,实际项目中需根据真实位置坐标计算
|
||
top: `${point.position?.index ? point.position.index * 20 : 20}px`,
|
||
left: '50px',
|
||
width: '300px',
|
||
height: '30px',
|
||
}}
|
||
/>
|
||
);
|
||
})} */}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 返回所有页面组件数组
|
||
return pages;
|
||
};
|
||
|
||
// 渲染文档内容
|
||
const renderDocumentContent = () => {
|
||
const real_path = fileContent.path || fileContent.template_contract_path || '';
|
||
|
||
// 如果路径无效,显示错误信息
|
||
if (!real_path) {
|
||
if(!fileContent.template_contract_path){
|
||
return (
|
||
<div className="text-red-500 p-4">
|
||
<p>无法加载文件:合同模板未上传</p>
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<div className="text-red-500 p-4">
|
||
<p>无法加载文件:路径无效</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// console.log('real_path',real_path);
|
||
// 获取文件扩展名
|
||
const fileExtension = real_path.split('.').pop()?.toLowerCase();
|
||
|
||
// PDF内容渲染
|
||
const renderPdfContent = () => (
|
||
<div
|
||
style={{
|
||
...styles.pdfContainer,
|
||
// 当缩放大于100%时设置最小宽度,确保出现横向滚动条
|
||
// minWidth: zoomLevel > 100 ? `${zoomLevel}%` : '100%',
|
||
width: '100%',
|
||
overflow: 'visible'
|
||
}}
|
||
>
|
||
<Document
|
||
file={DOCUMENT_URL+real_path}
|
||
onLoadSuccess={onDocumentLoadSuccess}
|
||
onLoadError={(error) => {
|
||
console.error("PDF加载错误:", error);
|
||
setLoadError("PDF文档加载失败:" + (error.message || "未知错误"));
|
||
}}
|
||
className="w-full"
|
||
error={<div className="text-red-500">PDF文档加载失败,请检查链接或网络连接。</div>}
|
||
noData={<div>无数据</div>}
|
||
loading={<div className="text-center py-10">PDF加载中...</div>}
|
||
>
|
||
{renderAllPages()}
|
||
</Document>
|
||
</div>
|
||
);
|
||
|
||
// 结构化数据渲染
|
||
const renderStructuredData = () => (
|
||
<div className="structured-view p-4 text-left mt-4 border-t border-gray-200">
|
||
<div className="text-xs font-medium mb-2">结构化数据:</div>
|
||
{fileContent.ocrResult ? (
|
||
<div className="overflow-auto max-h-[300px]">
|
||
<pre className="text-xs whitespace-pre-wrap bg-gray-50 p-3 rounded">
|
||
{JSON.stringify(fileContent.ocrResult, null, 2)}
|
||
</pre>
|
||
</div>
|
||
) : (
|
||
<div className="text-gray-500 p-4 text-center">
|
||
<p>无结构化数据可显示</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
|
||
// 根据文件类型选择不同的渲染方式
|
||
if (fileExtension === 'pdf') {
|
||
// 结构化视图模式:显示PDF和结构化数据
|
||
if (isStructuredView) {
|
||
return (
|
||
<div>
|
||
{renderPdfContent()}
|
||
{renderStructuredData()}
|
||
</div>
|
||
);
|
||
}
|
||
// 普通模式:仅显示PDF
|
||
return renderPdfContent();
|
||
} else {
|
||
// 非PDF文件显示不支持消息
|
||
return (
|
||
<div className="text-gray-500 p-4">
|
||
<p>暂不支持预览此类型的文件:{fileExtension}</p>
|
||
</div>
|
||
);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="file-preview">
|
||
<div className="file-preview-header px-2 text-xs sm:text-xs md:text-sm max-w-full flex items-center justify-between min-w-0">
|
||
<div className="flex items-center min-w-0 flex-shrink-0">
|
||
<i className={`${isStructuredView ? 'ri-file-list-line' : 'ri-file-text-line'} text-primary mr-2 flex-shrink-0`}></i>
|
||
<span className="font-medium text-primary truncate max-w-[120px]" title={isStructuredView ? '模板预览' : '文件预览'}>
|
||
{isStructuredView ? '模板预览' : '文件预览'}
|
||
</span>
|
||
</div>
|
||
<div className="file-preview-actions flex items-center ml-2 min-w-0 flex-1 justify-end overflow-hidden">
|
||
<button
|
||
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 flex-shrink-0"
|
||
onClick={handleScrollToTop}
|
||
title="返回顶部"
|
||
>
|
||
<i className="ri-arrow-up-double-line"></i>
|
||
<span className="ml-1 hidden sm:hidden md:hidden lg:hidden xl:inline truncate max-w-[60px]">返回顶部</span>
|
||
</button>
|
||
<button
|
||
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 flex-shrink-0"
|
||
onClick={handleZoomIn}
|
||
title="放大"
|
||
>
|
||
<i className="ri-zoom-in-line"></i>
|
||
</button>
|
||
<button
|
||
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 flex-shrink-0"
|
||
onClick={handleZoomOut}
|
||
title="缩小"
|
||
>
|
||
<i className="ri-zoom-out-line"></i>
|
||
</button>
|
||
{/* 页码跳转控件 */}
|
||
<div className="inline-flex items-center ml-2 flex-shrink-0">
|
||
<input
|
||
type="text"
|
||
className="ant-input ant-input-sm py-0 px-1 text-xs max-h-6 leading-5 w-[2.5rem] text-center
|
||
focus:outline-none focus:ring-1 focus:ring-green-900"
|
||
placeholder="页码"
|
||
value={pageInputValue}
|
||
onChange={handlePageInputChange}
|
||
onKeyDown={handlePageInputKeyDown}
|
||
/>
|
||
<button
|
||
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 ml-1"
|
||
onClick={handlePageJump}
|
||
disabled={!numPages}
|
||
title="跳转到页面"
|
||
>
|
||
<i className="ri-arrow-right-line"></i>
|
||
</button>
|
||
{numPages && (
|
||
<span className="ml-1 text-xs text-gray-500 hidden sm:hidden md:hidden lg:hidden xl:inline whitespace-nowrap">
|
||
/ {numPages}
|
||
</span>
|
||
)}
|
||
</div>
|
||
<span className="ml-2 text-xs text-gray-500 hidden sm:hidden md:hidden lg:hidden xl:inline whitespace-nowrap flex-shrink-0">
|
||
比例:{zoomLevel}%
|
||
</span>
|
||
<button
|
||
className={`ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 ml-2 flex-shrink-0 ${dragMode ? 'active bg-green-300' : ''}`}
|
||
title="切换拖拽模式"
|
||
aria-pressed={dragMode}
|
||
onClick={toggleDragMode}
|
||
>
|
||
<i className="ri-drag-move-line"></i>
|
||
<span className="ml-1 hidden sm:hidden md:hidden lg:hidden xl:inline truncate max-w-[80px]">
|
||
拖拽模式{dragMode ? '(已激活)' : ''}
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div
|
||
className="file-preview-content"
|
||
ref={contentRef}
|
||
style={{
|
||
maxHeight: 'calc(100vh - 150px)',
|
||
overflowY: 'auto',
|
||
overflowX: 'auto',
|
||
cursor: dragCursor,
|
||
userSelect: dragMode ? 'none' : 'auto', // 拖拽模式下防止文本被选中
|
||
}}
|
||
>
|
||
<button
|
||
className="pdf-interactive-container"
|
||
aria-label="PDF文档查看区域"
|
||
onMouseDown={handleMouseDown}
|
||
onMouseMove={handleMouseMove}
|
||
onMouseUp={handleMouseUp}
|
||
onKeyDown={(e) => {
|
||
// 添加键盘导航支持
|
||
const scrollAmount = 50;
|
||
if (e.key === 'ArrowUp') {
|
||
if (contentRef.current) contentRef.current.scrollTop -= scrollAmount;
|
||
e.preventDefault();
|
||
} else if (e.key === 'ArrowDown') {
|
||
if (contentRef.current) contentRef.current.scrollTop += scrollAmount;
|
||
e.preventDefault();
|
||
} else if (e.key === 'ArrowLeft') {
|
||
if (contentRef.current) contentRef.current.scrollLeft -= scrollAmount;
|
||
e.preventDefault();
|
||
} else if (e.key === 'ArrowRight') {
|
||
if (contentRef.current) contentRef.current.scrollLeft += scrollAmount;
|
||
e.preventDefault();
|
||
}
|
||
}}
|
||
style={{
|
||
position: 'relative',
|
||
height: '100%',
|
||
width: '100%',
|
||
display: 'block',
|
||
border: 'none',
|
||
background: 'transparent',
|
||
textAlign: 'center',
|
||
padding: 0
|
||
}}
|
||
>
|
||
{loadError ? (
|
||
<div className="text-red-500 p-4">
|
||
<p>{loadError}</p>
|
||
</div>
|
||
) : (
|
||
renderDocumentContent()
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|