@@ -80,6 +80,7 @@ export default function RebalanceHistoryTable() {
8080 const { user } = useAuth ( ) ;
8181 const { toast } = useToast ( ) ;
8282 const [ loading , setLoading ] = useState ( true ) ;
83+ const [ refreshing , setRefreshing ] = useState ( false ) ; // Separate state for refresh button
8384 const [ runningRebalances , setRunningRebalances ] = useState < RebalanceRequest [ ] > ( [ ] ) ;
8485 const [ completedRebalances , setCompletedRebalances ] = useState < RebalanceRequest [ ] > ( [ ] ) ;
8586 const [ cancelledRebalances , setCancelledRebalances ] = useState < RebalanceRequest [ ] > ( [ ] ) ;
@@ -125,7 +126,8 @@ export default function RebalanceHistoryTable() {
125126
126127 useEffect ( ( ) => {
127128 if ( user ) {
128- fetchRebalanceRequests ( ) ;
129+ // Pass true for isInitialLoad only when we haven't loaded data yet
130+ fetchRebalanceRequests ( ! initialLoadComplete ) ;
129131 }
130132 } , [ user , selectedDate ] ) ; // Reload when selectedDate changes
131133
@@ -149,15 +151,15 @@ export default function RebalanceHistoryTable() {
149151 ( payload ) => {
150152 // Only fetch if it's an update to a running rebalance
151153 if ( payload . new && runningRebalances . some ( r => r . id === payload . new . id ) ) {
152- fetchRebalanceRequests ( ) ;
154+ fetchRebalanceRequests ( false ) ; // Don't show loading on subscription updates
153155 }
154156 }
155157 )
156158 . subscribe ( ) ;
157159
158160 // Much slower polling - every 15 seconds instead of 3 seconds
159161 const interval = setInterval ( ( ) => {
160- fetchRebalanceRequests ( ) ;
162+ fetchRebalanceRequests ( false ) ; // Don't show loading on polling updates
161163 } , 15000 ) ; // Poll every 15 seconds for running rebalances
162164
163165 return ( ) => {
@@ -166,6 +168,12 @@ export default function RebalanceHistoryTable() {
166168 } ;
167169 } , [ user , runningRebalances . length > 0 , selectedDate , initialLoadComplete ] ) ; // Use boolean comparison
168170
171+ const handleManualRefresh = async ( ) => {
172+ setRefreshing ( true ) ;
173+ await fetchRebalanceRequests ( false ) ;
174+ setRefreshing ( false ) ;
175+ } ;
176+
169177 const fetchAnalysisDataForRebalance = async ( rebalanceId : string ) => {
170178 try {
171179 const { data, error } = await supabase
@@ -182,9 +190,14 @@ export default function RebalanceHistoryTable() {
182190 }
183191 } ;
184192
185- const fetchRebalanceRequests = async ( ) => {
193+ const fetchRebalanceRequests = async ( isInitialLoad = false ) => {
186194 if ( ! user ) return ;
187195
196+ // Only show loading state on initial load or when explicitly requested
197+ if ( isInitialLoad ) {
198+ setLoading ( true ) ;
199+ }
200+
188201 try {
189202 // Build date range for the selected date using local date parsing
190203 const [ year , month , day ] = selectedDate . split ( '-' ) . map ( Number ) ;
@@ -200,7 +213,27 @@ export default function RebalanceHistoryTable() {
200213 . lte ( 'created_at' , endOfDay . toISOString ( ) )
201214 . order ( 'created_at' , { ascending : false } ) ;
202215
203- if ( error ) throw error ;
216+ if ( error ) {
217+ console . error ( 'Error fetching rebalance requests:' , error ) ;
218+
219+ // Handle 500 errors gracefully - don't throw, just return
220+ if ( error . message ?. includes ( '500' ) || error . code === '500' ) {
221+ console . log ( 'Server error, skipping update' ) ;
222+ return ;
223+ }
224+
225+ // Handle authentication errors
226+ if ( error . code === 'PGRST301' || error . message ?. includes ( 'JWT' ) ) {
227+ console . log ( 'JWT error detected, will attempt refresh on next poll' ) ;
228+ // Try to refresh the session
229+ supabase . auth . refreshSession ( ) . catch ( err => {
230+ console . error ( 'Failed to refresh session:' , err ) ;
231+ } ) ;
232+ return ;
233+ }
234+
235+ throw error ;
236+ }
204237
205238 // Debug logging
206239 console . log ( 'Fetched rebalance requests:' , data ?. map ( r => ( {
@@ -243,32 +276,43 @@ export default function RebalanceHistoryTable() {
243276
244277 } catch ( error ) {
245278 console . error ( 'Error fetching rebalance requests:' , error ) ;
246- if ( ! loading ) {
279+ if ( ! initialLoadComplete ) { // Only show toast if not initial load
247280 toast ( {
248281 title : 'Error Loading History' ,
249282 description : 'Failed to load rebalance history. Please try again.' ,
250283 variant : 'destructive'
251284 } ) ;
252285 }
253286 } finally {
254- setLoading ( false ) ;
287+ if ( isInitialLoad ) {
288+ setLoading ( false ) ;
289+ }
255290 setInitialLoadComplete ( true ) ;
256291 }
257292 } ;
258293
259294 const handleDelete = async ( ) => {
260- if ( ! selectedRebalanceId ) return ;
295+ if ( ! selectedRebalanceId || ! user ) return ;
261296
262297 setDeleting ( true ) ;
263298 try {
299+ const { error : tradingError } = await supabase
300+ . from ( 'trading_actions' )
301+ . delete ( )
302+ . eq ( 'rebalance_request_id' , selectedRebalanceId )
303+ . eq ( 'user_id' , user . id ) ;
304+
305+ if ( tradingError && tradingError . code !== '23503' ) {
306+ throw tradingError ;
307+ }
308+
264309 // Delete related analyses first from analysis_history table
265310 const { error : analysesError } = await supabase
266311 . from ( 'analysis_history' )
267312 . delete ( )
268313 . eq ( 'rebalance_request_id' , selectedRebalanceId ) ;
269314
270315 if ( analysesError && analysesError . code !== '23503' ) {
271- // Ignore foreign key constraint errors as cascade delete should handle it
272316 console . warn ( 'Error deleting related analyses:' , analysesError ) ;
273317 }
274318
@@ -308,10 +352,16 @@ export default function RebalanceHistoryTable() {
308352
309353 setCancelling ( true ) ;
310354 try {
355+ // Find the item to be cancelled
356+ const itemToCancel = runningRebalances . find ( r => r . id === selectedRebalanceId ) ;
357+ if ( ! itemToCancel ) {
358+ throw new Error ( 'Rebalance not found in running list' ) ;
359+ }
360+
311361 // First check if the rebalance exists and belongs to the user
312362 const { data : checkData , error : checkError } = await supabase
313363 . from ( 'rebalance_requests' )
314- . select ( 'id, status ' )
364+ . select ( '* ' )
315365 . eq ( 'id' , selectedRebalanceId )
316366 . eq ( 'user_id' , user . id )
317367 . single ( ) ;
@@ -341,25 +391,59 @@ export default function RebalanceHistoryTable() {
341391 const { error : updateError } = await supabase
342392 . from ( 'rebalance_requests' )
343393 . update ( {
344- status : REBALANCE_STATUS . CANCELLED
394+ status : REBALANCE_STATUS . CANCELLED ,
395+ error_message : 'Cancelled by user'
345396 } )
346397 . eq ( 'id' , selectedRebalanceId )
347398 . eq ( 'user_id' , user . id ) ;
348399
349400 if ( updateError ) throw updateError ;
350401 }
351402
403+ // Gracefully move from running to cancelled list
404+ setRunningRebalances ( prev => prev . filter ( r => r . id !== selectedRebalanceId ) ) ;
405+
406+ // Create the cancelled item with all data from the database
407+ const cancelledItem : RebalanceRequest = {
408+ ...checkData ,
409+ status : REBALANCE_STATUS . CANCELLED ,
410+ error_message : checkData . error_message || 'Cancelled by user'
411+ } ;
412+
413+ setCancelledRebalances ( prev => [ cancelledItem , ...prev ] ) ;
414+
415+ // Also cancel all related analyses if any
416+ if ( analysisData [ selectedRebalanceId ] ) {
417+ const analysesToCancel = analysisData [ selectedRebalanceId ] ;
418+ // Update analysis statuses in the background
419+ analysesToCancel . forEach ( async ( analysis : any ) => {
420+ await supabase
421+ . from ( 'analysis_history' )
422+ . update ( {
423+ analysis_status : 'CANCELLED' ,
424+ updated_at : new Date ( ) . toISOString ( )
425+ } )
426+ . eq ( 'id' , analysis . id )
427+ . eq ( 'rebalance_request_id' , selectedRebalanceId ) ;
428+ } ) ;
429+
430+ // Remove from analysis data
431+ setAnalysisData ( prev => {
432+ const newData = { ...prev } ;
433+ delete newData [ selectedRebalanceId ] ;
434+ return newData ;
435+ } ) ;
436+ }
352437
353438 toast ( {
354439 title : 'Success' ,
355440 description : 'Rebalance cancelled successfully'
356441 } ) ;
357442
358- // Move from running to cancelled instead of refreshing everything
359- const cancelledItem = runningRebalances . find ( r => r . id === selectedRebalanceId ) ;
360- if ( cancelledItem ) {
361- setRunningRebalances ( prev => prev . filter ( r => r . id !== selectedRebalanceId ) ) ;
362- setCancelledRebalances ( prev => [ { ...cancelledItem , status : REBALANCE_STATUS . CANCELLED } , ...prev ] ) ;
443+ // Close modal if viewing this rebalance
444+ if ( selectedDetailId === selectedRebalanceId ) {
445+ setDetailModalOpen ( false ) ;
446+ setSelectedDetailId ( undefined ) ;
363447 }
364448 } catch ( error : any ) {
365449 console . error ( 'Error cancelling rebalance:' , error ) ;
@@ -595,12 +679,12 @@ export default function RebalanceHistoryTable() {
595679 < Button
596680 variant = "ghost"
597681 size = "sm"
598- onClick = { ( ) => fetchRebalanceRequests ( ) }
599- disabled = { loading }
682+ onClick = { handleManualRefresh }
683+ disabled = { refreshing }
600684 className = "h-8 w-8 p-0 hover:bg-[#fc0]/10 hover:text-[#fc0]"
601685 title = "Refresh"
602686 >
603- < RefreshCw className = { `h-4 w-4 ${ loading ? 'animate-spin' : '' } ` } />
687+ < RefreshCw className = { `h-4 w-4 ${ refreshing ? 'animate-spin' : '' } ` } />
604688 </ Button >
605689
606690 < div className = "w-px h-6 bg-border" />
@@ -1406,4 +1490,4 @@ export default function RebalanceHistoryTable() {
14061490 />
14071491 </ >
14081492 ) ;
1409- }
1493+ }
0 commit comments