fix(analytics): stabilize responsive row alignment

This commit is contained in:
Tam Nhu Tran committed 2026-02-13 06:28:46 +07:00
1 parent 43d441ce23
commit 0cc1f111f7
10 files changed
+40 -27

No files matched your search

@@ -32,7 +32,7 @@ export function CliproxyStatsCard({
if (isLoading) { if (isLoading) {
return ( return (
<Card className={cn('flex flex-col h-full', className)}> <Card className={cn('flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0', className)}>
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<Server className="h-4 w-4" /> <Server className="h-4 w-4" />
@@ -52,7 +52,12 @@ export function CliproxyStatsCard({
// Proxy not running // Proxy not running
if (!status?.running) { if (!status?.running) {
return ( return (
<Card className={cn('flex flex-col h-full border-dashed', className)}> <Card
className={cn(
'flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0 border-dashed',
className
)}
>
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
@@ -76,7 +81,12 @@ export function CliproxyStatsCard({
// Error fetching stats // Error fetching stats
if (error) { if (error) {
return ( return (
<Card className={cn('flex flex-col h-full border-destructive/50', className)}> <Card
className={cn(
'flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0 border-destructive/50',
className
)}
>
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
@@ -110,7 +120,7 @@ export function CliproxyStatsCard({
const maxModelRequests = models.length > 0 ? models[0][1] : 1; const maxModelRequests = models.length > 0 ? models[0][1] : 1;
return ( return (
<Card className={cn('flex flex-col h-full overflow-hidden', className)}> <Card className={cn('flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0', className)}>
<CardHeader className="px-3 py-2 border-b bg-muted/5"> <CardHeader className="px-3 py-2 border-b bg-muted/5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
@@ -70,7 +70,7 @@ export function DateRangeFilter({
}; };
return ( return (
<div className={cn('flex items-center gap-2', className)}> <div className={cn('flex flex-wrap items-center gap-2', className)}>
{presets.map((preset) => ( {presets.map((preset) => (
<Button <Button
key={preset.label} key={preset.label}
@@ -87,7 +87,7 @@ export function DateRangeFilter({
id="date" id="date"
variant={'outline'} variant={'outline'}
className={cn( className={cn(
'w-auto min-w-[240px] justify-start text-left font-normal', 'w-auto min-w-[200px] sm:min-w-[240px] justify-start text-left font-normal',
!value && 'text-muted-foreground' !value && 'text-muted-foreground'
)} )}
> >
@@ -34,12 +34,12 @@ export function ModelBreakdownChart({ data, isLoading, className }: ModelBreakdo
}, [data]); }, [data]);
if (isLoading) { if (isLoading) {
return <Skeleton className={cn('h-[300px] w-full', className)} />; return <Skeleton className={cn('h-full min-h-[100px] w-full', className)} />;
} }
if (!data || data.length === 0) { if (!data || data.length === 0) {
return ( return (
<div className={cn('h-[300px] flex items-center justify-center', className)}> <div className={cn('h-full min-h-[100px] flex items-center justify-center', className)}>
<p className="text-muted-foreground">No model data available</p> <p className="text-muted-foreground">No model data available</p>
</div> </div>
); );
@@ -72,8 +72,8 @@ export function ModelBreakdownChart({ data, isLoading, className }: ModelBreakdo
}; };
return ( return (
<div className={cn('w-full', className)}> <div className={cn('w-full h-full min-h-[100px]', className)}>
<ResponsiveContainer width="100%" height={250}> <ResponsiveContainer width="100%" height="100%">
<PieChart> <PieChart>
<Pie <Pie
data={chartData} data={chartData}
@@ -55,7 +55,7 @@ export function SessionStatsCard({ data, isLoading, className }: SessionStatsCar
if (isLoading) { if (isLoading) {
return ( return (
<Card className={cn('flex flex-col h-full', className)}> <Card className={cn('flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0', className)}>
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<Skeleton className="h-5 w-32" /> <Skeleton className="h-5 w-32" />
</CardHeader> </CardHeader>
@@ -68,7 +68,7 @@ export function SessionStatsCard({ data, isLoading, className }: SessionStatsCar
if (!stats) { if (!stats) {
return ( return (
<Card className={cn('flex flex-col h-full', className)}> <Card className={cn('flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0', className)}>
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<Terminal className="w-4 h-4" /> <Terminal className="w-4 h-4" />
@@ -83,14 +83,16 @@ export function SessionStatsCard({ data, isLoading, className }: SessionStatsCar
} }
return ( return (
<Card className={cn('flex flex-col h-full shadow-sm', className)}> <Card
className={cn('flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0 shadow-sm', className)}
>
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<Terminal className="w-4 h-4" /> <Terminal className="w-4 h-4" />
Session Stats Session Stats
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="px-3 pb-3 pt-0 flex-1 flex flex-col gap-4"> <CardContent className="px-3 pb-3 pt-0 flex-1 min-h-0 flex flex-col gap-3">
{/* Key Metrics Grid */} {/* Key Metrics Grid */}
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
{/* Total Sessions */} {/* Total Sessions */}
@@ -119,12 +121,12 @@ export function SessionStatsCard({ data, isLoading, className }: SessionStatsCar
</div> </div>
{/* Recent Activity List */} {/* Recent Activity List */}
<div className="flex-1 space-y-2"> <div className="flex-1 min-h-0 space-y-2">
<div className="flex items-center gap-1 text-xs text-muted-foreground font-medium mb-1"> <div className="flex items-center gap-1 text-xs text-muted-foreground font-medium mb-1">
<Clock className="w-3 h-3" /> <Clock className="w-3 h-3" />
Recent Activity Recent Activity
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5 max-h-full overflow-y-auto pr-1">
{stats.recentSessions.map((session) => ( {stats.recentSessions.map((session) => (
<div <div
key={session.sessionId} key={session.sessionId}
@@ -23,7 +23,7 @@ export function UsageSummaryCards({ data, isLoading }: UsageSummaryCardsProps) {
if (isLoading) { if (isLoading) {
return ( return (
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-5 gap-4"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
{[1, 2, 3, 4, 5].map((i) => ( {[1, 2, 3, 4, 5].map((i) => (
<Card key={i}> <Card key={i}>
<CardContent className="p-6"> <CardContent className="p-6">
@@ -95,7 +95,7 @@ export function UsageSummaryCards({ data, isLoading }: UsageSummaryCardsProps) {
]; ];
return ( return (
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-5 gap-4"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
{cards.map((card, index) => { {cards.map((card, index) => {
const Icon = card.icon; const Icon = card.icon;
return ( return (
+1 -1
View File
@@ -109,7 +109,7 @@ function SidebarProvider({
} as React.CSSProperties } as React.CSSProperties
} }
className={cn( className={cn(
'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', 'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex h-svh min-h-0 w-full overflow-hidden',
className className
)} )}
{...props} {...props}
@@ -30,12 +30,12 @@ export function AnalyticsHeader({
viewMode, viewMode,
}: AnalyticsHeaderProps) { }: AnalyticsHeaderProps) {
return ( return (
<div className="flex items-center justify-between shrink-0"> <div className="flex flex-col gap-3 shrink-0 xl:flex-row xl:items-center xl:justify-between">
<div> <div>
<h1 className="text-xl font-semibold">Analytics</h1> <h1 className="text-xl font-semibold">Analytics</h1>
<p className="text-sm text-muted-foreground">Track usage & insights</p> <p className="text-sm text-muted-foreground">Track usage & insights</p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2 xl:justify-end">
<Button <Button
variant={viewMode === 'hourly' ? 'default' : 'outline'} variant={viewMode === 'hourly' ? 'default' : 'outline'}
size="sm" size="sm"
@@ -45,6 +45,7 @@ export function AnalyticsHeader({
24H 24H
</Button> </Button>
<DateRangeFilter <DateRangeFilter
className="flex-wrap"
value={dateRange} value={dateRange}
onChange={onDateRangeChange} onChange={onDateRangeChange}
presets={[ presets={[
@@ -44,9 +44,9 @@ export function ChartsGrid({
const { privacyMode } = usePrivacy(); const { privacyMode } = usePrivacy();
return ( return (
<div className="flex-1 flex flex-col min-h-0 gap-4"> <div className="min-h-0 grid gap-4 lg:grid-rows-[minmax(260px,1.2fr)_minmax(220px,0.9fr)]">
{/* Usage Trend Chart - Full Width */} {/* Usage Trend Chart - Full Width */}
<Card className="flex flex-col flex-1 min-h-0 max-h-[500px] overflow-hidden shadow-sm"> <Card className="flex flex-col h-full min-h-[220px] lg:min-h-[240px] overflow-hidden gap-0 py-0 shadow-sm">
<CardHeader className="px-3 py-2 shrink-0"> <CardHeader className="px-3 py-2 shrink-0">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<TrendingUp className="w-4 h-4" /> <TrendingUp className="w-4 h-4" />
@@ -63,7 +63,7 @@ export function ChartsGrid({
</Card> </Card>
{/* Bottom Row */} {/* Bottom Row */}
<div className="grid grid-cols-1 lg:grid-cols-10 gap-4 h-auto lg:h-[180px] shrink-0"> <div className="grid grid-cols-1 lg:grid-cols-10 gap-4 h-auto min-h-[220px] lg:h-full lg:min-h-[220px] lg:grid-rows-[minmax(0,1fr)] lg:[&>*]:min-h-0">
{/* Cost by Model */} {/* Cost by Model */}
<CostByModelCard <CostByModelCard
models={models} models={models}
@@ -73,7 +73,7 @@ export function ChartsGrid({
/> />
{/* Model Distribution */} {/* Model Distribution */}
<Card className="flex flex-col h-full min-h-0 shadow-sm lg:col-span-2"> <Card className="flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0 shadow-sm lg:col-span-2">
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<PieChart className="w-4 h-4" /> <PieChart className="w-4 h-4" />
@@ -26,7 +26,7 @@ export function CostByModelCard({
privacyMode, privacyMode,
}: CostByModelCardProps) { }: CostByModelCardProps) {
return ( return (
<Card className="flex flex-col h-full min-h-0 shadow-sm lg:col-span-4"> <Card className="flex flex-col h-full min-h-0 overflow-hidden gap-0 py-0 shadow-sm lg:col-span-4">
<CardHeader className="px-3 py-2"> <CardHeader className="px-3 py-2">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<DollarSign className="w-4 h-4" /> <DollarSign className="w-4 h-4" />
+1 -1
View File
@@ -40,7 +40,7 @@ export function AnalyticsPage() {
} = useAnalyticsPage(); } = useAnalyticsPage();
return ( return (
<div className="flex flex-col h-full overflow-hidden px-4 pt-4 pb-50 gap-4"> <div className="grid h-full min-h-0 grid-rows-[auto_auto_minmax(0,1fr)] gap-4 overflow-y-auto px-4 py-4">
{/* Header */} {/* Header */}
<AnalyticsHeader <AnalyticsHeader
dateRange={dateRange} dateRange={dateRange}