@@ -11,6 +11,8 @@ interface DocumentLoadSuccess {
1111 numPages : number ;
1212}
1313
14+ const PAGE_INIT_DELAY = 800 ;
15+
1416const PDFViewer = ( ) => {
1517 const [ numPages , setNumPages ] = useState < number | null > ( null ) ;
1618 const [ pageNumber , setPageNumber ] = useState ( 1 ) ;
@@ -24,81 +26,96 @@ const PDFViewer = () => {
2426 null
2527 ) ;
2628
27- const manualScrollInProgress = useRef ( false ) ;
28- const PAGE_INIT_DELAY = 800 ;
29-
3029 const headerRef = useRef < HTMLDivElement > ( null ) ;
3130 const containerRef = useRef < HTMLDivElement > ( null ) ;
3231 const contentRef = useRef < HTMLDivElement > ( null ) ;
3332 const pageRefs = useRef < Record < number , HTMLDivElement | null > > ( { } ) ;
34- const initializationRef = useRef ( false ) ;
33+ const prevGuidRef = useRef < string | null > ( null ) ;
34+ const isFetchingRef = useRef ( false ) ;
3535
3636 const location = useLocation ( ) ;
3737 const navigate = useNavigate ( ) ;
3838 const params = new URLSearchParams ( location . search ) ;
3939 const guid = params . get ( "guid" ) ;
4040 const pageParam = params . get ( "page" ) ;
4141
42- const baseURL = import . meta. env . VITE_API_BASE_URL ;
43- const pdfUrl = useMemo (
44- ( ) => ( guid ? `${ baseURL } /v1/api/uploadFile/${ guid } ` : null ) ,
45- [ guid , baseURL ]
46- ) ;
42+ const baseURL = import . meta. env . VITE_API_BASE_URL as string | undefined ;
43+
44+ const pdfUrl = useMemo ( ( ) => {
45+ const url = guid && baseURL ? `${ baseURL } /v1/api/uploadFile/${ guid } ` : null ;
46+
47+ return url ;
48+ } , [ guid , baseURL ] ) ;
4749
4850 useEffect ( ( ) => {
49- pageRefs . current = { } ;
50- setIsDocumentLoaded ( false ) ;
51- initializationRef . current = false ;
51+ const nextPage = pageParam ? parseInt ( pageParam , 10 ) : 1 ;
52+ const guidChanged = guid !== prevGuidRef . current ;
53+
54+ if ( guidChanged ) {
55+ pageRefs . current = { } ;
56+ setIsDocumentLoaded ( false ) ;
57+ setNumPages ( null ) ;
58+ setPdfData ( null ) ;
59+ setPageNumber ( 1 ) ;
60+ }
5261
53- if ( pageParam ) {
54- const page = parseInt ( pageParam , 10 ) ;
55- if ( ! isNaN ( page ) && page > 0 ) setTargetPageAfterLoad ( page ) ;
62+ if ( ! isNaN ( nextPage ) && nextPage > 0 ) {
63+ setTargetPageAfterLoad ( nextPage ) ;
5664 } else {
5765 setTargetPageAfterLoad ( 1 ) ;
5866 }
59- } , [ guid , pageParam ] ) ;
67+
68+ prevGuidRef . current = guid ;
69+ } , [ guid , pageParam , location . pathname , location . search ] ) ;
6070
6171 const scrollToPage = useCallback (
6272 ( page : number ) => {
63- if ( page < 1 || ! numPages || page > numPages ) return ;
73+ if ( ! numPages || page < 1 || page > numPages ) {
74+ return ;
75+ }
76+
6477 const targetRef = pageRefs . current [ page ] ;
65- if ( ! targetRef ) return ;
66-
67- manualScrollInProgress . current = true ;
68- targetRef . scrollIntoView ( { behavior : "smooth" , block : "start" } ) ;
69-
70- const observer = new IntersectionObserver (
71- ( entries , obs ) => {
72- const entry = entries [ 0 ] ;
73- if ( entry ?. isIntersecting ) {
74- manualScrollInProgress . current = false ;
75- obs . disconnect ( ) ;
76- }
77- } ,
78- { threshold : 0.5 }
79- ) ;
80- observer . observe ( targetRef ) ;
78+ if ( ! targetRef ) {
79+ setTimeout ( ( ) => scrollToPage ( page ) , 100 ) ;
80+ return ;
81+ }
8182
82- const newParams = new URLSearchParams ( location . search ) ;
83- newParams . set ( "page" , String ( page ) ) ;
84- navigate ( ` ${ location . pathname } ? ${ newParams . toString ( ) } ` , {
85- replace : true ,
83+ targetRef . scrollIntoView ( {
84+ behavior : "smooth" ,
85+ block : "start" ,
86+ inline : "nearest" ,
8687 } ) ;
88+
89+ const newParams = new URLSearchParams ( location . search ) ;
90+ const oldPage = newParams . get ( "page" ) ;
91+ if ( oldPage !== String ( page ) ) {
92+ newParams . set ( "page" , String ( page ) ) ;
93+ const newUrl = `${ location . pathname } ?${ newParams . toString ( ) } ` ;
94+ navigate ( newUrl , { replace : true } ) ;
95+ }
96+
8797 setPageNumber ( page ) ;
8898 } ,
89- [ numPages , navigate , location . pathname , location . search ]
99+ [ numPages , location . pathname , location . search , navigate , pageNumber ]
90100 ) ;
91101
102+ // Preload-aware navigation: if not loaded yet, just remember target page.
92103 const goToPage = useCallback (
93104 ( page : number ) => {
94105 if ( typeof page !== "number" || isNaN ( page ) ) return ;
95- if ( page < 1 ) page = 1 ;
96- else if ( numPages && page > numPages ) page = numPages ;
97106
98- setPageNumber ( page ) ;
99- scrollToPage ( page ) ;
107+ const clamped = Math . max ( 1 , numPages ? Math . min ( page , numPages ) : page ) ;
108+
109+ if ( ! isDocumentLoaded || ! numPages ) {
110+ setTargetPageAfterLoad ( clamped ) ;
111+ return ;
112+ }
113+
114+ if ( clamped === pageNumber ) return ;
115+ setPageNumber ( clamped ) ;
116+ scrollToPage ( clamped ) ;
100117 } ,
101- [ numPages , scrollToPage ]
118+ [ isDocumentLoaded , numPages , pageNumber , scrollToPage ]
102119 ) ;
103120
104121 useEffect ( ( ) => {
@@ -115,21 +132,16 @@ const PDFViewer = () => {
115132 } , [ goToPage ] ) ;
116133
117134 useEffect ( ( ) => {
118- if (
119- isDocumentLoaded &&
120- numPages &&
121- targetPageAfterLoad &&
122- Object . keys ( pageRefs . current ) . length > 0
123- ) {
135+ if ( isDocumentLoaded && numPages && targetPageAfterLoad ) {
124136 const validPage = Math . min ( Math . max ( 1 , targetPageAfterLoad ) , numPages ) ;
125137 setPageNumber ( validPage ) ;
126138
127- const timeoutId = setTimeout ( ( ) => {
139+ const timer = setTimeout ( ( ) => {
128140 scrollToPage ( validPage ) ;
129141 setTargetPageAfterLoad ( null ) ;
130142 } , PAGE_INIT_DELAY ) ;
131143
132- return ( ) => clearTimeout ( timeoutId ) ;
144+ return ( ) => clearTimeout ( timer ) ;
133145 }
134146 } , [ isDocumentLoaded , numPages , targetPageAfterLoad , scrollToPage ] ) ;
135147
@@ -169,7 +181,13 @@ const PDFViewer = () => {
169181
170182 const fetchPdf = useCallback ( async ( ) => {
171183 if ( ! pdfUrl ) return ;
184+ if ( isFetchingRef . current ) {
185+ console . log ( "⏳ fetchPdf already in progress, skipping duplicate call" ) ;
186+ return ;
187+ }
188+
172189 try {
190+ isFetchingRef . current = true ;
173191 setLoading ( true ) ;
174192 setError ( null ) ;
175193 const token = localStorage . getItem ( "access" ) ;
@@ -193,6 +211,7 @@ const PDFViewer = () => {
193211 setPdfData ( null ) ;
194212 } finally {
195213 setLoading ( false ) ;
214+ isFetchingRef . current = false ;
196215 }
197216 } , [ pdfUrl , isPDF ] ) ;
198217
@@ -230,13 +249,11 @@ const PDFViewer = () => {
230249 ←
231250 </ button >
232251 < span className = "text-sm" >
233- Page { pageNumber } of { numPages || "-" }
252+ Page { pageNumber } of { numPages ?? "-" }
234253 </ span >
235254 < button
236- onClick = { ( ) =>
237- goToPage ( Math . min ( pageNumber + 1 , numPages || pageNumber ) )
238- }
239- disabled = { ! numPages || pageNumber >= numPages }
255+ onClick = { ( ) => goToPage ( pageNumber + 1 ) }
256+ disabled = { ! numPages || pageNumber >= ( numPages ?? 1 ) }
240257 className = "px-3 py-1 bg-white border rounded"
241258 >
242259 →
@@ -282,6 +299,7 @@ const PDFViewer = () => {
282299 style = { { maxHeight : containerSize . height } }
283300 >
284301 < Document
302+ key = { guid ?? "doc" }
285303 file = { file }
286304 onLoadSuccess = { onDocumentLoadSuccess }
287305 onLoadError = { ( err ) => setError ( err . message ) }
@@ -294,18 +312,17 @@ const PDFViewer = () => {
294312 < div
295313 key = { pageNum }
296314 ref = { ( el ) => {
297- if ( el ) pageRefs . current [ pageNum ] = el ;
315+ pageRefs . current [ pageNum ] = el ;
298316 } }
299317 className = "mb-4 w-full"
318+ data-page = { pageNum }
300319 >
301320 < Page
302321 pageNumber = { pageNum }
303- scale = { scale }
322+ width = { Math . max ( 0 , ( containerSize . width || 0 ) - 50 ) }
304323 renderTextLayer = { true }
305- renderAnnotationLayer = { true }
324+ renderAnnotationLayer = { false }
306325 className = "shadow-lg"
307- height = { containerSize . height || undefined }
308- width = { ( containerSize . width || 0 ) - 50 }
309326 />
310327 < div className = "text-center text-gray-500 text-sm mt-1" >
311328 Page { pageNum } of { numPages }
0 commit comments