Skip to content

feat: improve dashboard UI and model comparison - #1664

Merged
smakosh merged 2 commits into
mainfrom
feat/dashboard-ui-improvements
Feb 14, 2026
Merged

smakosh merged 2 commits into
mainfrom
feat/dashboard-ui-improvements

Conversation

@smakosh

@smakosh smakosh commented Feb 14, 2026

Copy link
Copy Markdown
Member

Summary

  • Add custom date range picker with 7d, 30d, and custom calendar options to usage/dashboard pages
  • Fix overview cost chart (split into separate renders for costs vs requests)
  • Remove CartesianGrid dashed lines from overview charts
  • Rewrite cost breakdown as shadcn donut chart with per-model breakdown, provider colors, and compact cost formatting (K/M/B)
  • Fix admin "All Time Credits" column showing $0.00 for all organizations (Drizzle subquery issue with snake_case casing)
  • Improve model comparison page: increase font sizes throughout, add Description, Released Date, Stability, Web Search, JSON Schema, and Output Types attributes; remove unused Aliases row
  • Fix lint errors in shadcn chart component (unused var, context value memoization)

Test plan

  • Verify date range picker works on usage and dashboard pages (7d, 30d, custom range)
  • Verify overview chart renders both costs and requests modes correctly
  • Verify cost breakdown donut chart displays per-model costs with correct colors
  • Verify admin dashboard shows correct All Time Credits values
  • Verify model comparison page shows all new attributes with larger text
  • Verify model comparison works with different model pairs

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Interactive Date Range Picker and Calendar component for flexible date selection.
  • Improvements

    • Charts refreshed: area-style visuals, improved tooltips/legends, and a new shared chart system.
    • Cost breakdown redesigned with segment details and percentages; layout sizing made more responsive.
    • Model comparison expanded with release dates, stability, and additional metadata.
    • Admin activity and credit calculations optimized for broader date-range queries.
  • Chores

    • Updated chart/date dependencies.

- Add custom date range picker with 7d/30d/custom calendar
- Fix overview cost chart by splitting into separate renders
- Remove CartesianGrid dashed lines from charts
- Rewrite cost breakdown as shadcn donut chart with model breakdown
- Format large costs as K/M/B in donut center
- Fix admin All Time Credits showing $0.00
- Improve model comparison: bigger text, add description,
  released date, stability, web search, JSON schema,
  output types; remove aliases row

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Replaces days-based dashboard filtering with explicit from/to date ranges: adds a DateRangePicker, calendar and chart infra, updates API /activity to accept from/to (with fallback), converts UI pages and charts to use from/to, and batches admin per-organization credit calculations.

Changes

Cohort / File(s) Summary
API Route Updates
apps/api/src/routes/activity.ts, apps/api/src/routes/admin.ts
activity.ts now accepts optional from/to (strings) and falls back to days when absent; query param destructuring updated. admin.ts replaces per-org inline credit subquery with a batched/grouped query and maps totals into the response.
Date Range UI & Helpers
apps/ui/src/components/date-range-picker.tsx, apps/ui/src/lib/components/calendar.tsx
Adds DateRangePicker component (presets + custom calendar) and a Calendar wrapper around react-day-picker; exposes getDateRangeFromParams helper and URL-building/navigation for date ranges.
Charting Infrastructure
apps/ui/src/lib/components/chart.tsx
New chart system: ChartConfig, ChartContainer, ChartStyle, ChartTooltip, ChartLegend, and helper utilities for theme-aware colors and payload-config resolution; exported types/components for reuse.
Dashboard Pages
apps/ui/src/app/dashboard/[orgId]/[projectId]/page.tsx, apps/ui/src/app/dashboard/[orgId]/[projectId]/model-usage/page.tsx, apps/ui/src/app/dashboard/[orgId]/[projectId]/usage/page.tsx
Search params types extended to include optional from/to; pages compute formatted from/to defaults and call APIs with from/to instead of days.
Dashboard Components
apps/ui/src/components/dashboard/activity-chart.tsx, .../overview.tsx, .../dashboard-client.tsx, .../cost-breakdown-card.tsx
Charts and client components replaced days logic with from/to via getDateRangeFromParams; Overview switches LineChart→AreaChart with gradients and removes days prop; dashboard-client swaps Tabs for DateRangePicker; cost card height constraint removed.
Usage Components & Charts
apps/ui/src/components/usage/*, apps/ui/src/components/usage/cost-breakdown-chart.tsx
All usage charts and tables now use from/to derived from getDateRangeFromParams; cost-breakdown-chart rewritten to aggregate costs per model/provider, add provider color mapping, responsive layout, and detailed list view. model-usage-client removes orgId prop and integrates DateRangePicker.
Model Comparison
apps/ui/src/components/models/model-comparison.tsx
Public model shape changed: removed aliases from ModelDetail; ComparisonRowKey expanded with description, releasedAt, jsonOutputSchema, webSearch, outputTypes; rendering adjusted to show new fields and layout/typography tweaks.
Deps & New Components
apps/ui/package.json, apps/ui/src/lib/components/calendar.tsx
Adds react-day-picker dependency and updates recharts version; new Calendar component added.
Tests
apps/api/src/routes/activity.spec.ts
Updated test: omitting date params now expects a successful 200 with activity array (defaults to 7 days) instead of a 400 error.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant DateRangePicker
    participant Router
    participant DashboardPage
    participant API
    participant Chart

    User->>DateRangePicker: choose date range (preset/custom)
    DateRangePicker->>DateRangePicker: format as yyyy-MM-dd
    DateRangePicker->>Router: router.push(URL with from/to)
    Router->>DashboardPage: update searchParams (from/to)
    DashboardPage->>DashboardPage: extract from/to and build query
    DashboardPage->>API: GET /activity?from=...&to=... (&projectId,&apiKeyId)
    API->>API: query DB for records within from..to
    API-->>DashboardPage: return activity data
    DashboardPage->>Chart: pass activity + from/to
    Chart->>Chart: generate dateRange & aggregate per day
    Chart->>User: render chart (area/pie/etc.)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • steebchen
🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'improve dashboard UI and model comparison' is partially related to the changeset but lacks specificity about the main changes. While UI improvements are mentioned, the title doesn't capture key changes like the date range picker addition, cost breakdown chart rewrite, or the admin credits fix.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dashboard-ui-improvements

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/api/src/routes/activity.spec.ts (1)

243-254: Redundant Authorization header.

Line 246 includes Authorization: "Bearer test-token" alongside the Cookie: token header. No other test in this file uses the Bearer header — they all authenticate via Cookie alone. This is unlikely to cause a failure but is inconsistent with the rest of the test suite.

Suggested cleanup
 	test("GET /activity should default to 7 days when no date params provided", async () => {
 		const res = await app.request("/activity", {
 			headers: {
-				Authorization: "Bearer test-token",
 				Cookie: token,
 			},
 		});

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@smakosh smakosh self-assigned this Feb 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ui/src/components/usage/model-usage-client.tsx (1)

24-101: ⚠️ Potential issue | 🟡 Minor

Missing useEffect redirect for default date range params.

Unlike usage-client.tsx and dashboard-client.tsx, this component doesn't redirect to add from/to URL params when they're absent. This means the URL won't reflect the active date range until the user interacts with the picker, creating an inconsistency across pages. The data will still load correctly via getDateRangeFromParams defaults, so this is minor.

apps/ui/src/app/dashboard/[orgId]/[projectId]/usage/page.tsx (1)

8-23: ⚠️ Potential issue | 🔴 Critical

from and to should come from searchParams, not params.

In Next.js App Router, params contains dynamic route segments (e.g., orgId, projectId), while query string parameters like ?from=2026-01-01&to=2026-01-07 are provided via searchParams. Since from and to are query parameters (not URL path segments), they will never be populated from params.

Compare with apps/ui/src/app/dashboard/[orgId]/[projectId]/page.tsx (Lines 10–17), which correctly reads from/to from searchParams.

Proposed fix
 export default async function UsagePage({
 	params,
+	searchParams,
 }: {
 	params?: Promise<{
 		projectId?: string;
-		days?: string;
-		from?: string;
-		to?: string;
 	}>;
+	searchParams?: Promise<{
+		from?: string;
+		to?: string;
+	}>;
 }) {
 	const paramsData = await params;
 	const projectId = paramsData?.projectId;
+	const searchParamsData = searchParams ? await searchParams : {};
 
 	const today = new Date();
-	const fromParam = paramsData?.from || format(subDays(today, 6), "yyyy-MM-dd");
-	const toParam = paramsData?.to || format(today, "yyyy-MM-dd");
+	const fromParam = searchParamsData?.from || format(subDays(today, 6), "yyyy-MM-dd");
+	const toParam = searchParamsData?.to || format(today, "yyyy-MM-dd");
🤖 Fix all issues with AI agents
In `@apps/api/src/routes/activity.ts`:
- Around line 79-80: The from/to query parameters currently use
z.string().optional() and accept any string; change their Zod schema to validate
date strings (e.g., use z.string().optional().refine(val => !val ||
!isNaN(Date.parse(val)), { message: "invalid date" }) or use z.preprocess to
parse then z.date()) so invalid values like "abc" are rejected; update the
schema that defines from and to in apps/api/src/routes/activity.ts (the from and
to entries shown) and ensure the route handler that consumes them still expects
the validated/parsed format.
- Around line 115-123: The code silently falls back to days-based range when
only one of from/to is provided; update the request validation in the activity
route handler to require both or neither: if (from && to) keep the current
startDate/endDate logic; else if (from || to) immediately return
res.status(400).json({ error: "Both 'from' and 'to' must be provided together"
}); otherwise (neither provided) continue using days (effectiveDays) to compute
startDate/endDate. Ensure you reference the existing variables (from, to, days,
startDate, endDate) and apply the early 400 response in the same scope where
those variables are currently set.

In `@apps/ui/src/lib/components/chart.tsx`:
- Around line 244-248: The tooltip currently uses a falsy check "item.value &&
(...)" which hides valid zero values; change the condition to explicitly allow 0
and only exclude null/undefined/invalid numbers — e.g., replace the check with
Number.isFinite(item.value) (or an equivalent null/NaN guard like item.value !=
null && !Number.isNaN(item.value)) before rendering the span so 0 renders but
NaN/undefined do not; update the condition around the span in chart.tsx where
item.value is used.
🧹 Nitpick comments (12)
apps/ui/src/components/date-range-picker.tsx (2)

24-40: getDateRangeFromParams default range has a subtle inconsistency with the useEffect redirects in consumer components.

In getDateRangeFromParams (line 35-39), when no params exist, from is created via subDays(today, 6) and to is new Date(). These Date objects include the current time component. However, when params do exist (lines 30-31), the dates are constructed with T00:00:00 — always midnight.

This means the first render (before the useEffect redirect fires) will have time-of-day embedded in from/to, while subsequent renders will have midnight. This could cause isPresetActive (which compares formatted yyyy-MM-dd strings) to still work, but it's worth noting for any downstream code that does time-based comparisons.

Normalize the default dates to midnight for consistency
 	const today = new Date();
+	today.setHours(0, 0, 0, 0);
 	return {
 		from: subDays(today, 6),
 		to: today,
 	};

42-49: isPresetActive instantiates new Date() on every call, which may drift from the from/to computed earlier in the same render.

Since getDateRangeFromParams and isPresetActive each call new Date() independently, if the clock crosses midnight between calls, the active tab could be incorrect (e.g., showing "custom" instead of "7days"). This is an extremely rare edge case with a safe fallback ("custom" tab), so it's not critical.

apps/ui/src/components/usage/usage-client.tsx (1)

74-84: Duplicated useEffect redirect logic across multiple components.

This exact useEffect pattern (check for missing from/to, delete days, set defaults, router.replace) is repeated in usage-client.tsx (here) and dashboard-client.tsx (lines 76-85). Consider extracting into a shared hook (e.g., useEnsureDateRangeParams) to reduce duplication.

apps/ui/src/components/dashboard/dashboard-client.tsx (1)

317-317: DateRangePicker placement is outside the header row, unlike other pages.

In usage-client.tsx and model-usage-client.tsx, the DateRangePicker is placed inline with the header/controls in a flex row. Here it's placed as a standalone element below the header (line 317), creating a slightly different layout pattern. This may be intentional given the dashboard's more complex header, but worth confirming for visual consistency.

apps/ui/src/components/usage/cost-breakdown-chart.tsx (2)

21-21: Deep import from recharts internals may break on version updates.

recharts/types/util/types is not part of the public API surface. Consider defining a local ViewBox type or using the type from Recharts' top-level exports if available.

Local type alternative
-import type { ViewBox } from "recharts/types/util/types";
+
+type ViewBox = { cx?: number; cy?: number; innerRadius?: number; outerRadius?: number };

292-325: Legend item color resolution is brittle — relies on checking "color" in config.

ChartConfig entries use a discriminated union: the color property is present only when theme is absent, and vice versa. The "color" in config check on line 306 will be true even if color is undefined (since the key exists). This means the fallback "#94a3b8" may not trigger when intended.

Since you already set color in the config (line 154), this works in practice for this component. However, consider simplifying:

Simpler color resolution
 style={{
 	backgroundColor:
-		(config && "color" in config
-			? config.color
-			: undefined) || "#94a3b8",
+		config?.color ?? "#94a3b8",
 }}
apps/ui/src/app/dashboard/[orgId]/[projectId]/model-usage/page.tsx (1)

13-18: Remove unused days parameter from searchParams type.

The days?: string property on line 14 is a leftover from the old parameterization and is never read. Keeping it in the type may confuse future readers into thinking it's still supported.

Proposed fix
 	searchParams?: Promise<{
-		days?: string;
 		from?: string;
 		to?: string;
 		apiKeyId?: string;
 	}>;
apps/ui/src/components/usage/error-rate-chart.tsx (1)

61-63: Date range derivation and dateRange construction are duplicated across chart components.

The exact same pattern (lines 61–63 for from/to/fromStr/toStr and lines 126–132 for totalDays/dateRange construction) is repeated verbatim in error-rate-chart.tsx, cache-rate-chart.tsx, and usage-chart.tsx. Consider extracting a shared utility, e.g., buildDateRange(from: Date, to: Date): string[], to reduce duplication.

Example shared utility
// e.g., in a shared date-utils.ts
import { addDays, differenceInCalendarDays, format } from "date-fns";

export function buildDateRange(from: Date, to: Date): string[] {
	const totalDays = differenceInCalendarDays(to, from) + 1;
	const range: string[] = [];
	for (let i = 0; i < totalDays; i++) {
		range.push(format(addDays(from, i), "yyyy-MM-dd"));
	}
	return range;
}

Also applies to: 126-132

apps/ui/src/app/dashboard/[orgId]/[projectId]/page.tsx (1)

13-17: days is unused in the searchParams type.

The days field is still declared in the type but never referenced in the function body. Consider removing it to keep the interface clean and avoid confusion.

Proposed fix
 	searchParams?: Promise<{
-		days?: string;
 		from?: string;
 		to?: string;
 	}>;
apps/ui/src/components/dashboard/activity-chart.tsx (1)

454-468: Redundant getUniqueModels recomputation in render.

getUniqueModels(data.activity) is called three times inside JSX (Lines 454, 455, 463), but the result is already stored in uniqueModels on Line 355. Reuse the existing variable to avoid redundant set construction on every render.

Proposed fix
-						{getUniqueModels(data.activity).length > 0 ? (
-							getUniqueModels(data.activity).map((model, index) => (
+						{uniqueModels.length > 0 ? (
+							uniqueModels.map((model, index) => (
 								<Bar
 									key={`${model}-${index}`}
 									dataKey={model}
 									name={model}
 									stackId="models"
 									fill={getModelColor(model, index)}
 									radius={
-										index === getUniqueModels(data.activity).length - 1
+										index === uniqueModels.length - 1
 											? [4, 4, 0, 0]
 											: [0, 0, 0, 0]
 									}
 								/>
apps/ui/src/components/dashboard/overview.tsx (2)

177-189: Tooltip content passes hardcoded dummy props that are misleading.

The active={true}, payload={[{ value: 0 }]}, and label="tooltip" are placeholder values that get overridden at runtime by recharts via cloneElement. However, this pattern is confusing and fragile — if someone reads this code, they may think these are the actual values. The same pattern appears on Lines 262–270.

The idiomatic recharts approach is to pass the component as a function or only pass custom props:

Proposed fix (costs chart, apply same pattern to requests chart)
 				<Tooltip
-					content={
-						<CustomTooltip
-							active={true}
-							payload={[{ value: 0 }]}
-							label="tooltip"
-							metric="costs"
-						/>
-					}
+					content={(props) => <CustomTooltip {...props} metric="costs" />}
 					cursor={{
 						fill: "color-mix(in srgb, currentColor 15%, transparent)",
 					}}
 				/>

97-97: totalDays is computed but never used.

totalDays is calculated on Line 97 but not referenced anywhere in the component's output. It appears to be leftover from the migration away from the days prop.

Proposed fix
-	const totalDays = differenceInCalendarDays(to, from) + 1;
 	const dateRange: string[] = [];
 
-	for (let i = 0; i < totalDays; i++) {
+	const totalDays = differenceInCalendarDays(to, from) + 1;
+	for (let i = 0; i < totalDays; i++) {

Or simply inline it:

-	const totalDays = differenceInCalendarDays(to, from) + 1;
 	const dateRange: string[] = [];
 
-	for (let i = 0; i < totalDays; i++) {
+	for (let i = 0; i <= differenceInCalendarDays(to, from); i++) {

Comment thread apps/api/src/routes/activity.ts
Comment thread apps/api/src/routes/activity.ts
Comment thread apps/ui/src/lib/components/chart.tsx
@smakosh
smakosh enabled auto-merge February 14, 2026 00:10
@smakosh
smakosh disabled auto-merge February 14, 2026 00:13
The days parameter is now optional (defaults to 7) since
from/to date range params were added as alternatives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@smakosh
smakosh added this pull request to the merge queue Feb 14, 2026
Merged via the queue into main with commit 5060f6a Feb 14, 2026
22 checks passed
@smakosh
smakosh deleted the feat/dashboard-ui-improvements branch February 14, 2026 00:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant