1- // src/components/ui/VsumsPanel.tsx
21import React , { useEffect , useRef , useState , useCallback } from 'react' ;
32import { apiService } from '../../services/api' ;
43import { Vsum } from '../../types' ;
@@ -92,75 +91,77 @@ export const VsumsPanel: React.FC = () => {
9291 const [ search , setSearch ] = useState ( '' ) ;
9392 const [ page , setPage ] = useState ( 0 ) ;
9493 const [ hasMore , setHasMore ] = useState ( true ) ;
95-
9694 const [ showCreate , setShowCreate ] = useState ( false ) ;
9795 const [ detailsId , setDetailsId ] = useState < number | null > ( null ) ;
9896
9997 const containerRef = useRef < HTMLDivElement | null > ( null ) ;
10098 const rafRef = useRef < number | null > ( null ) ;
99+ const requestSeq = useRef ( 0 ) ;
101100 const PAGE_SIZE = 10 ;
102101
103102 const formatDateTime = ( iso : string ) => {
104103 const d = new Date ( iso ) ;
105104 return `${ d . toLocaleDateString ( ) } ${ d . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) } ` ;
106105 } ;
107106
108- // Load first page (stable: only depends on search)
109107 const loadFirstPage = useCallback ( async ( ) => {
108+ const mySeq = ++ requestSeq . current ;
110109 setLoading ( true ) ;
111110 setError ( '' ) ;
111+ setItems ( [ ] ) ;
112+ setPage ( 0 ) ;
113+ setHasMore ( true ) ;
114+ if ( containerRef . current ) containerRef . current . scrollTop = 0 ;
112115 try {
113116 const res = await apiService . getVsumsPaginated ( search , 0 , PAGE_SIZE ) ;
117+ if ( mySeq !== requestSeq . current ) return ;
114118 const newData : Vsum [ ] = res . data || [ ] ;
115119 setItems ( newData ) ;
116120 setPage ( 1 ) ;
117121 setHasMore ( newData . length === PAGE_SIZE ) ;
118122 } catch ( e ) {
123+ if ( mySeq !== requestSeq . current ) return ;
119124 setError ( e instanceof Error ? e . message : 'Failed to load VSUMs' ) ;
120125 } finally {
121- setLoading ( false ) ;
126+ if ( mySeq === requestSeq . current ) setLoading ( false ) ;
122127 }
123128 } , [ search ] ) ;
124129
125- // Load next page for infinite scroll
126130 const loadNextPage = useCallback ( async ( ) => {
127131 if ( loading || ! hasMore ) return ;
132+ const mySeq = requestSeq . current ;
128133 setLoading ( true ) ;
129134 setError ( '' ) ;
130135 try {
131136 const res = await apiService . getVsumsPaginated ( search , page , PAGE_SIZE ) ;
137+ if ( mySeq !== requestSeq . current ) return ;
132138 const newData : Vsum [ ] = res . data || [ ] ;
133139 setItems ( prev => [ ...prev , ...newData ] ) ;
134140 setPage ( prev => prev + 1 ) ;
135141 setHasMore ( newData . length === PAGE_SIZE ) ;
136142 } catch ( e ) {
143+ if ( mySeq !== requestSeq . current ) return ;
137144 setError ( e instanceof Error ? e . message : 'Failed to load VSUMs' ) ;
138145 } finally {
139- setLoading ( false ) ;
146+ if ( mySeq === requestSeq . current ) setLoading ( false ) ;
140147 }
141148 } , [ search , page , hasMore , loading ] ) ;
142149
143- // Reload list whenever search changes
144150 useEffect ( ( ) => {
145151 loadFirstPage ( ) ;
146152 } , [ loadFirstPage ] ) ;
147153
148- // Infinite scroll listener (throttled with rAF)
149154 useEffect ( ( ) => {
150155 const el = containerRef . current ;
151156 if ( ! el ) return ;
152-
153157 const onScroll = ( ) => {
154158 if ( rafRef . current ) cancelAnimationFrame ( rafRef . current ) ;
155159 rafRef . current = requestAnimationFrame ( ( ) => {
156160 if ( loading || ! hasMore ) return ;
157161 const nearBottom = el . scrollHeight - el . scrollTop - el . clientHeight < 150 ;
158- if ( nearBottom ) {
159- loadNextPage ( ) ;
160- }
162+ if ( nearBottom ) loadNextPage ( ) ;
161163 } ) ;
162164 } ;
163-
164165 el . addEventListener ( 'scroll' , onScroll ) ;
165166 return ( ) => {
166167 el . removeEventListener ( 'scroll' , onScroll ) ;
@@ -180,7 +181,6 @@ export const VsumsPanel: React.FC = () => {
180181
181182 { error && < div style = { errorStyle } > { error } </ div > }
182183
183- { /* Create first */ }
184184 < button
185185 onClick = { ( ) => setShowCreate ( true ) }
186186 style = { createButtonStyle }
@@ -190,7 +190,6 @@ export const VsumsPanel: React.FC = () => {
190190 Create
191191 </ button >
192192
193- { /* Search */ }
194193 < div style = { { display : 'flex' , gap : 8 , marginBottom : 12 } } >
195194 < input
196195 type = "text"
@@ -256,14 +255,12 @@ export const VsumsPanel: React.FC = () => {
256255 </ div >
257256 ) ) }
258257
259- { /* Empty state (plain text) */ }
260258 { ! loading && items . length === 0 && (
261259 < div style = { { textAlign : 'center' , color : '#6b7280' , marginTop : 40 , fontStyle : 'italic' } } >
262260 No VSUMs found. Create one to get started.
263261 </ div >
264262 ) }
265263
266- { /* Loading pinned at bottom */ }
267264 { loading && (
268265 < div style = { { padding : 12 , fontStyle : 'italic' , color : '#5a6c7d' , marginTop : 12 } } >
269266 Loading...
0 commit comments