import { useState, useRef, useEffect } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js'; const DocumentViewer = ({ selectedFile, documentData, onPageChange }) => { const pdfContainerRef = useRef(null); const [numPages, setNumPages] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [zoomLevel, setZoomLevel] = useState(1); const [visiblePages, setVisiblePages] = useState(new Set([1])); const [containerWidth, setContainerWidth] = useState(0); // Update container width on mount and resize useEffect(() => { const updateContainerWidth = () => { if (pdfContainerRef.current) { const width = pdfContainerRef.current.clientWidth; // Subtract padding and some margin for optimal viewing const availableWidth = width - 32; // 16px padding on each side setContainerWidth(availableWidth); } }; updateContainerWidth(); window.addEventListener('resize', updateContainerWidth); return () => window.removeEventListener('resize', updateContainerWidth); }, []); // Expose goToPage function to parent component (only once when component mounts) useEffect(() => { if (onPageChange) { onPageChange({ goToPage }); } }, [onPageChange]); // Calculate optimal page width const getPageWidth = () => { if (!containerWidth) return 600; // Fallback // Use container width minus some margin, but respect zoom level const baseWidth = Math.min(containerWidth * 0.99, 2000); // Max 800px for readability return baseWidth * zoomLevel; }; // Handle scroll to update current page and track visible pages const handleScroll = () => { if (!pdfContainerRef.current || !numPages) return; const container = pdfContainerRef.current; const scrollTop = container.scrollTop; const containerHeight = container.clientHeight; const totalScrollHeight = container.scrollHeight - containerHeight; // Calculate which page we're viewing based on scroll position const scrollPercent = scrollTop / totalScrollHeight; const newPage = Math.min(Math.floor(scrollPercent * numPages) + 1, numPages); if (newPage !== currentPage) { setCurrentPage(newPage); } // Track visible pages based on zoom level const newVisiblePages = new Set(); const visibleRange = Math.max(1, Math.ceil(2 / zoomLevel)); for (let i = Math.max(1, newPage - visibleRange); i <= Math.min(numPages, newPage + visibleRange); i++) { newVisiblePages.add(i); } // Update visible pages if changed if (newVisiblePages.size !== visiblePages.size || ![...newVisiblePages].every(page => visiblePages.has(page))) { setVisiblePages(newVisiblePages); } }; // Jump to specific page const goToPage = (pageNumber) => { if (!pdfContainerRef.current || !numPages || pageNumber < 1 || pageNumber > numPages) return; // Update visible pages immediately for target page const newVisiblePages = new Set(); const visibleRange = Math.max(1, Math.ceil(2 / zoomLevel)); for (let i = Math.max(1, pageNumber - visibleRange); i <= Math.min(numPages, pageNumber + visibleRange); i++) { newVisiblePages.add(i); } setVisiblePages(newVisiblePages); // Use setTimeout to ensure pages are rendered before scrolling setTimeout(() => { const container = pdfContainerRef.current; if (!container) return; // Find the target page element by its data attribute or position const pageElements = container.querySelectorAll('[data-page-number]'); let targetElement = null; // If we can't find elements by data attribute, calculate position manually if (pageElements.length === 0) { // Calculate approximate position based on page height // Each page has some margin (mb-4 = 16px) plus the actual page height const containerHeight = container.clientHeight; const totalContent = container.scrollHeight; const avgPageHeight = totalContent / numPages; const targetPosition = (pageNumber - 1) * avgPageHeight; // Center the page in viewport const scrollPosition = Math.max(0, targetPosition - containerHeight / 4); container.scrollTo({ top: scrollPosition, behavior: 'smooth' }); } else { // Find the specific page element for (const element of pageElements) { if (parseInt(element.getAttribute('data-page-number')) === pageNumber) { targetElement = element; break; } } if (targetElement) { // Scroll to center the page in viewport const elementRect = targetElement.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); const scrollOffset = container.scrollTop; const targetPosition = scrollOffset + elementRect.top - containerRect.top - (container.clientHeight - elementRect.height) / 4; container.scrollTo({ top: Math.max(0, targetPosition), behavior: 'smooth' }); } } }, 100); // Small delay to ensure rendering }; // Zoom controls const zoomIn = () => setZoomLevel(prev => Math.min(prev + 0.25, 3)); const zoomOut = () => setZoomLevel(prev => Math.max(prev - 0.25, 0.5)); const resetZoom = () => setZoomLevel(1); if (!selectedFile) { return (

No PDF selected

); } return (

{documentData?.filename || 'Document'}

{/* PDF Container */}
setNumPages(numPages)} > {/* Render all pages continuously */} {numPages && Array.from(new Array(numPages), (_, index) => { const pageNum = index + 1; const isVisible = visiblePages.has(pageNum); return (
); })}
{/* Pagination overlay - floating pill */} {numPages && (
{currentPage}/{numPages}
)} {/* Zoom controls overlay - bottom right */} {numPages && (
{/* Main zoom pill - vertical */}
{/* Reset button below */}
)}
); }; export default DocumentViewer;