@@ -3,6 +3,8 @@ import { useNavigate } from 'react-router-dom';
33import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card' ;
44import { Badge } from '@/components/ui/badge' ;
55import { Button } from '@/components/ui/button' ;
6+ import { Popover , PopoverContent , PopoverTrigger } from "@/components/ui/popover" ;
7+ import { Calendar } from "@/components/ui/calendar" ;
68import {
79 AlertDialog ,
810 AlertDialogAction ,
@@ -36,7 +38,10 @@ import {
3638 MoreVertical ,
3739 StopCircle ,
3840 TrendingUp ,
39- TrendingDown
41+ TrendingDown ,
42+ Calendar as CalendarIcon ,
43+ ChevronLeft ,
44+ ChevronRight
4045} from 'lucide-react' ;
4146import RebalanceDetailModal from './RebalanceDetailModal' ;
4247import {
@@ -87,9 +92,46 @@ export default function RebalanceHistoryTable() {
8792 const [ deleting , setDeleting ] = useState ( false ) ;
8893 const [ analysisData , setAnalysisData ] = useState < { [ key : string ] : any [ ] } > ( { } ) ;
8994
95+ // Date filter states - default to today (using local date to avoid timezone issues)
96+ const today = new Date ( ) ;
97+ const todayString = `${ today . getFullYear ( ) } -${ String ( today . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( today . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
98+ const [ selectedDate , setSelectedDate ] = useState < string > ( todayString ) ;
99+
100+ // Track if initial data has been loaded
101+ const [ initialLoadComplete , setInitialLoadComplete ] = useState ( false ) ;
102+
103+ // Helper to format date display
104+ const getDateDisplay = ( ) => {
105+ const today = new Date ( ) ;
106+ const todayString = `${ today . getFullYear ( ) } -${ String ( today . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( today . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
107+
108+ const yesterday = new Date ( ) ;
109+ yesterday . setDate ( yesterday . getDate ( ) - 1 ) ;
110+ const yesterdayString = `${ yesterday . getFullYear ( ) } -${ String ( yesterday . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( yesterday . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
111+
112+ if ( selectedDate === todayString ) return "Today" ;
113+ if ( selectedDate === yesterdayString ) return "Yesterday" ;
114+
115+ // Parse the date parts to avoid timezone issues
116+ const [ year , month , day ] = selectedDate . split ( '-' ) . map ( Number ) ;
117+ const date = new Date ( year , month - 1 , day ) ;
118+
119+ return date . toLocaleDateString ( 'en-US' , {
120+ month : 'short' ,
121+ day : 'numeric' ,
122+ year : 'numeric'
123+ } ) ;
124+ } ;
125+
90126 useEffect ( ( ) => {
91127 if ( user ) {
92128 fetchRebalanceRequests ( ) ;
129+ }
130+ } , [ user , selectedDate ] ) ; // Reload when selectedDate changes
131+
132+ // Separate useEffect for polling and subscriptions
133+ useEffect ( ( ) => {
134+ if ( user && runningRebalances . length > 0 ) {
93135 // Set up real-time subscription for instant updates
94136 const subscription = supabase
95137 . channel ( 'rebalance_updates' )
@@ -139,10 +181,18 @@ export default function RebalanceHistoryTable() {
139181 if ( ! user ) return ;
140182
141183 try {
184+ // Build date range for the selected date using local date parsing
185+ const [ year , month , day ] = selectedDate . split ( '-' ) . map ( Number ) ;
186+ const startOfDay = new Date ( year , month - 1 , day , 0 , 0 , 0 , 0 ) ;
187+ const endOfDay = new Date ( year , month - 1 , day , 23 , 59 , 59 , 999 ) ;
188+
189+ // Query only rebalances from the selected date
142190 const { data, error } = await supabase
143191 . from ( 'rebalance_requests' )
144192 . select ( '*' )
145193 . eq ( 'user_id' , user . id )
194+ . gte ( 'created_at' , startOfDay . toISOString ( ) )
195+ . lte ( 'created_at' , endOfDay . toISOString ( ) )
146196 . order ( 'created_at' , { ascending : false } ) ;
147197
148198 if ( error ) throw error ;
@@ -197,6 +247,7 @@ export default function RebalanceHistoryTable() {
197247 }
198248 } finally {
199249 setLoading ( false ) ;
250+ setInitialLoadComplete ( true ) ;
200251 }
201252 } ;
202253
@@ -351,7 +402,7 @@ export default function RebalanceHistoryTable() {
351402 return 'outline' ;
352403 }
353404 } ;
354-
405+
355406 const getStatusClassName = ( status : string ) : string => {
356407 // Convert legacy status to new format for consistent variant display
357408 const normalizedStatus = convertLegacyRebalanceStatus ( status ) ;
@@ -526,6 +577,134 @@ export default function RebalanceHistoryTable() {
526577 < >
527578 < Card >
528579 < CardContent className = "pt-6" >
580+ < div className = "flex items-center justify-between mb-4" >
581+ < h3 className = "text-lg font-semibold" > Rebalance History</ h3 >
582+ < div className = "flex items-center gap-1" >
583+ < Button
584+ variant = "ghost"
585+ size = "sm"
586+ onClick = { ( ) => {
587+ const [ year , month , day ] = selectedDate . split ( '-' ) . map ( Number ) ;
588+ const prevDate = new Date ( year , month - 1 , day ) ;
589+ prevDate . setDate ( prevDate . getDate ( ) - 1 ) ;
590+ const prevYear = prevDate . getFullYear ( ) ;
591+ const prevMonth = String ( prevDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
592+ const prevDay = String ( prevDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
593+ setSelectedDate ( `${ prevYear } -${ prevMonth } -${ prevDay } ` ) ;
594+ } }
595+ className = "h-8 w-8 p-0 hover:bg-[#fc0]/10 hover:text-[#fc0]"
596+ >
597+ < ChevronLeft className = "h-4 w-4" />
598+ </ Button >
599+
600+ < Popover >
601+ < PopoverTrigger asChild >
602+ < Button
603+ variant = "outline"
604+ size = "sm"
605+ className = "px-3 min-w-[140px] hover:border-[#fc0] hover:bg-[#fc0]/10 hover:text-[#fc0] transition-all duration-200"
606+ >
607+ < CalendarIcon className = "h-4 w-4 mr-2" />
608+ { getDateDisplay ( ) }
609+ </ Button >
610+ </ PopoverTrigger >
611+ < PopoverContent
612+ className = "w-auto p-0 bg-background border-border"
613+ align = "center"
614+ >
615+ < div className = "space-y-2 p-3" >
616+ < div className = "flex gap-2" >
617+ < Button
618+ size = "sm"
619+ variant = "outline"
620+ className = "flex-1 text-xs hover:bg-[#fc0]/10 hover:border-[#fc0]/50 hover:text-[#fc0]"
621+ onClick = { ( ) => {
622+ const yesterday = new Date ( ) ;
623+ yesterday . setDate ( yesterday . getDate ( ) - 1 ) ;
624+ const year = yesterday . getFullYear ( ) ;
625+ const month = String ( yesterday . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
626+ const day = String ( yesterday . getDate ( ) ) . padStart ( 2 , '0' ) ;
627+ setSelectedDate ( `${ year } -${ month } -${ day } ` ) ;
628+ } }
629+ >
630+ Yesterday
631+ </ Button >
632+ < Button
633+ size = "sm"
634+ variant = "outline"
635+ className = "flex-1 text-xs hover:bg-[#fc0]/10 hover:border-[#fc0]/50 hover:text-[#fc0]"
636+ onClick = { ( ) => {
637+ const today = new Date ( ) ;
638+ const year = today . getFullYear ( ) ;
639+ const month = String ( today . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
640+ const day = String ( today . getDate ( ) ) . padStart ( 2 , '0' ) ;
641+ setSelectedDate ( `${ year } -${ month } -${ day } ` ) ;
642+ } }
643+ >
644+ Today
645+ </ Button >
646+ </ div >
647+ </ div >
648+ < Calendar
649+ mode = "single"
650+ selected = { ( ( ) => {
651+ // Parse the date string properly to avoid timezone issues
652+ const [ year , month , day ] = selectedDate . split ( '-' ) . map ( Number ) ;
653+ return new Date ( year , month - 1 , day ) ;
654+ } ) ( ) }
655+ onSelect = { ( date ) => {
656+ if ( date ) {
657+ // Format the date properly without timezone issues
658+ const year = date . getFullYear ( ) ;
659+ const month = String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
660+ const day = String ( date . getDate ( ) ) . padStart ( 2 , '0' ) ;
661+ setSelectedDate ( `${ year } -${ month } -${ day } ` ) ;
662+ }
663+ } }
664+ disabled = { ( date ) => {
665+ const today = new Date ( ) ;
666+ today . setHours ( 23 , 59 , 59 , 999 ) ;
667+ return date > today ;
668+ } }
669+ showOutsideDays = { false }
670+ initialFocus
671+ className = "rounded-b-lg"
672+ />
673+ </ PopoverContent >
674+ </ Popover >
675+
676+ < Button
677+ variant = "ghost"
678+ size = "sm"
679+ onClick = { ( ) => {
680+ const [ year , month , day ] = selectedDate . split ( '-' ) . map ( Number ) ;
681+ const nextDate = new Date ( year , month - 1 , day ) ;
682+ nextDate . setDate ( nextDate . getDate ( ) + 1 ) ;
683+
684+ const today = new Date ( ) ;
685+ const todayString = `${ today . getFullYear ( ) } -${ String ( today . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( today . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
686+
687+ const nextYear = nextDate . getFullYear ( ) ;
688+ const nextMonth = String ( nextDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
689+ const nextDay = String ( nextDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
690+ const next = `${ nextYear } -${ nextMonth } -${ nextDay } ` ;
691+
692+ if ( next <= todayString ) {
693+ setSelectedDate ( next ) ;
694+ }
695+ } }
696+ disabled = { ( ( ) => {
697+ const today = new Date ( ) ;
698+ const todayString = `${ today . getFullYear ( ) } -${ String ( today . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( today . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
699+ return selectedDate === todayString ;
700+ } ) ( ) }
701+ className = "h-8 w-8 p-0 hover:bg-[#fc0]/10 hover:text-[#fc0] disabled:hover:bg-transparent disabled:hover:text-muted-foreground"
702+ >
703+ < ChevronRight className = "h-4 w-4" />
704+ </ Button >
705+ </ div >
706+ </ div >
707+
529708 < Tabs defaultValue = "all" className = "space-y-4" >
530709 < TabsList className = "grid w-full grid-cols-4" >
531710 < TabsTrigger value = "all" >
@@ -800,17 +979,45 @@ export default function RebalanceHistoryTable() {
800979 </ div >
801980 ) }
802981
803- { totalCount === 0 && (
804- < div className = "text-center py-8" >
805- < p className = "text-muted-foreground" > No rebalance records found</ p >
982+ { ! initialLoadComplete || loading ? (
983+ < div className = "flex items-center justify-center py-8" >
984+ < Loader2 className = "h-6 w-6 animate-spin mr-2" />
985+ < span className = "text-muted-foreground" > Loading rebalances...</ span >
806986 </ div >
987+ ) : (
988+ totalCount === 0 && (
989+ < div className = "flex items-center justify-center py-8" >
990+ < img
991+ src = "/goose_sit.png"
992+ alt = "No data"
993+ className = "w-32 h-32 mr-6"
994+ />
995+ < div className = "text-left text-muted-foreground" >
996+ < p > No rebalances on { getDateDisplay ( ) } </ p >
997+ < p className = "text-sm mt-2" > Try selecting a different date or start a new rebalance</ p >
998+ </ div >
999+ </ div >
1000+ )
8071001 ) }
8081002 </ TabsContent >
8091003
8101004 < TabsContent value = "running" className = "space-y-4" >
811- { runningRebalances . length === 0 ? (
812- < div className = "text-center py-8" >
813- < p className = "text-muted-foreground" > No running rebalances</ p >
1005+ { ! initialLoadComplete || loading ? (
1006+ < div className = "flex items-center justify-center py-8" >
1007+ < Loader2 className = "h-6 w-6 animate-spin mr-2" />
1008+ < span className = "text-muted-foreground" > Loading rebalances...</ span >
1009+ </ div >
1010+ ) : runningRebalances . length === 0 ? (
1011+ < div className = "flex items-center justify-center py-8" >
1012+ < img
1013+ src = "/goose_sit.png"
1014+ alt = "No data"
1015+ className = "w-32 h-32 mr-6"
1016+ />
1017+ < div className = "text-left text-muted-foreground" >
1018+ < p > No running rebalances on { getDateDisplay ( ) } </ p >
1019+ < p className = "text-sm mt-2" > Select a different date to view more rebalances</ p >
1020+ </ div >
8141021 </ div >
8151022 ) : (
8161023 < div className = "space-y-2" >
@@ -912,9 +1119,22 @@ export default function RebalanceHistoryTable() {
9121119 </ TabsContent >
9131120
9141121 < TabsContent value = "completed" className = "space-y-4" >
915- { completedRebalances . length === 0 ? (
916- < div className = "text-center py-8" >
917- < p className = "text-muted-foreground" > No completed rebalances</ p >
1122+ { ! initialLoadComplete || loading ? (
1123+ < div className = "flex items-center justify-center py-8" >
1124+ < Loader2 className = "h-6 w-6 animate-spin mr-2" />
1125+ < span className = "text-muted-foreground" > Loading rebalances...</ span >
1126+ </ div >
1127+ ) : completedRebalances . length === 0 ? (
1128+ < div className = "flex items-center justify-center py-8" >
1129+ < img
1130+ src = "/goose_sit.png"
1131+ alt = "No data"
1132+ className = "w-32 h-32 mr-6"
1133+ />
1134+ < div className = "text-left text-muted-foreground" >
1135+ < p > No completed rebalances on { getDateDisplay ( ) } </ p >
1136+ < p className = "text-sm mt-2" > Select a different date to view more rebalances</ p >
1137+ </ div >
9181138 </ div >
9191139 ) : (
9201140 < div className = "space-y-2" >
@@ -993,9 +1213,22 @@ export default function RebalanceHistoryTable() {
9931213 </ TabsContent >
9941214
9951215 < TabsContent value = "cancelled" className = "space-y-4" >
996- { cancelledRebalances . length === 0 ? (
997- < div className = "text-center py-8" >
998- < p className = "text-muted-foreground" > No cancelled rebalances</ p >
1216+ { ! initialLoadComplete || loading ? (
1217+ < div className = "flex items-center justify-center py-8" >
1218+ < Loader2 className = "h-6 w-6 animate-spin mr-2" />
1219+ < span className = "text-muted-foreground" > Loading rebalances...</ span >
1220+ </ div >
1221+ ) : cancelledRebalances . length === 0 ? (
1222+ < div className = "flex items-center justify-center py-8" >
1223+ < img
1224+ src = "/goose_sit.png"
1225+ alt = "No data"
1226+ className = "w-32 h-32 mr-6"
1227+ />
1228+ < div className = "text-left text-muted-foreground" >
1229+ < p > No cancelled rebalances on { getDateDisplay ( ) } </ p >
1230+ < p className = "text-sm mt-2" > Select a different date to view more rebalances</ p >
1231+ </ div >
9991232 </ div >
10001233 ) : (
10011234 < div className = "space-y-2" >
0 commit comments