Skip to content

Commit 55296c2

Browse files
committed
Add date filtering and UI improvements to history tables
Introduces date filtering with calendar popovers to both RebalanceHistoryTable and UnifiedAnalysisHistory, allowing users to view records by specific day. Updates empty states with new goose images, improves loading indicators, and enhances calendar and date input styling for better UX. Switches to Supabase publishable key in environment and workflow files, and refactors type imports in auth. Also adds new goose images and updates NotFound page to use the new image.
1 parent ae1a00f commit 55296c2

File tree

15 files changed

+801
-231
lines changed

15 files changed

+801
-231
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Supabase Configuration
22
VITE_SUPABASE_URL=your_supabase_url_here
3-
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
3+
# Use the new publishable key format (recommended by Supabase, starts with sb_publishable_)
4+
VITE_SUPABASE_PUBLISHABLE_KEY=your_supabase_publishable_key_here
5+
# Legacy anon key (deprecated, only for backward compatibility)
6+
# VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
47

58
# Development
69
APP_ENV=development

.github/workflows/deploy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ jobs:
3737
env:
3838
# Public environment variables (safe to expose)
3939
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
40+
# Use the new publishable key format (recommended by Supabase)
41+
VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_PUBLISHABLE_KEY }}
42+
# Legacy anon key (fallback for compatibility)
4043
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
4144
# PostHog analytics
4245
VITE_POSTHOG_API_KEY: ${{ secrets.VITE_POSTHOG_API_KEY }}

public/goose_stare.png

514 KB
Loading

public/goose_walk.png

564 KB
Loading

src/components/RebalanceHistoryTable.tsx

Lines changed: 247 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useNavigate } from 'react-router-dom';
33
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
44
import { Badge } from '@/components/ui/badge';
55
import { Button } from '@/components/ui/button';
6+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
7+
import { Calendar } from "@/components/ui/calendar";
68
import {
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';
4146
import RebalanceDetailModal from './RebalanceDetailModal';
4247
import {
@@ -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

Comments
 (0)