Skip to content

Commit 0462aa6

Browse files
committed
Nice chart blank states
1 parent ed64032 commit 0462aa6

File tree

4 files changed

+39
-116
lines changed

4 files changed

+39
-116
lines changed

apps/webapp/app/components/code/QueryResultsChart.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { OutputColumnMetadata } from "@internal/clickhouse";
2+
import { BarChart3, LineChart } from "lucide-react";
23
import { memo, useMemo } from "react";
34
import type { ChartConfig } from "~/components/primitives/charts/Chart";
45
import { Chart } from "~/components/primitives/charts/ChartCompound";
5-
import { Paragraph } from "../primitives/Paragraph";
6+
import { ChartBlankState } from "../primitives/charts/ChartBlankState";
67
import type { AggregationType, ChartConfiguration } from "../metrics/QueryWidget";
78
import { aggregateValues } from "../primitives/charts/aggregation";
89
import { getRunStatusHexColor } from "~/components/runs/v3/TaskRunStatus";
@@ -947,20 +948,22 @@ export const QueryResultsChart = memo(function QueryResultsChart({
947948
}, [isDateBased, xAxisTickFormatter, xAxisAngle]);
948949

949950
// Validation — all hooks must be above this point
951+
const chartIcon = chartType === "bar" ? BarChart3 : LineChart;
952+
950953
if (!xAxisColumn) {
951-
return <EmptyState message="Select an X-axis column to display the chart" />;
954+
return <ChartBlankState icon={chartIcon} message="Select an X-axis column to display the chart" />;
952955
}
953956

954957
if (yAxisColumns.length === 0) {
955-
return <EmptyState message="Select a Y-axis column to display the chart" />;
958+
return <ChartBlankState icon={chartIcon} message="Select a Y-axis column to display the chart" />;
956959
}
957960

958961
if (rows.length === 0) {
959-
return <EmptyState message="No data to display" />;
962+
return <ChartBlankState icon={chartIcon} message="No data to display" />;
960963
}
961964

962965
if (data.length === 0) {
963-
return <EmptyState message="Unable to transform data for chart" />;
966+
return <ChartBlankState icon={chartIcon} message="Unable to transform data for chart" />;
964967
}
965968

966969
// Base x-axis props shared by all chart types
@@ -1113,12 +1116,3 @@ function createYAxisFormatter(data: Record<string, unknown>[], series: string[])
11131116
};
11141117
}
11151118

1116-
function EmptyState({ message }: { message: string }) {
1117-
return (
1118-
<div className="flex h-full min-h-[300px] items-center justify-center">
1119-
<Paragraph variant="small" className="text-text-dimmed">
1120-
{message}
1121-
</Paragraph>
1122-
</div>
1123-
);
1124-
}

apps/webapp/app/components/code/TSQLResultsTable.tsx

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ChevronDownIcon, ChevronUpDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid";
22
import type { OutputColumnMetadata } from "@internal/clickhouse";
3-
import { IconFilter2, IconFilter2X } from "@tabler/icons-react";
3+
import { IconFilter2, IconFilter2X, IconTable } from "@tabler/icons-react";
4+
import { ChartBlankState } from "../primitives/charts/ChartBlankState";
45
import { rankItem } from "@tanstack/match-sorter-utils";
56
import {
67
flexRender,
@@ -37,7 +38,7 @@ import { useProject } from "~/hooks/useProject";
3738
import { cn } from "~/utils/cn";
3839
import { formatCurrencyAccurate, formatNumber } from "~/utils/numberFormatter";
3940
import { v3ProjectPath, v3RunPathFromFriendlyId } from "~/utils/pathBuilder";
40-
import { Paragraph } from "../primitives/Paragraph";
41+
4142
import { TextLink } from "../primitives/TextLink";
4243
import { InfoIconTooltip, SimpleTooltip } from "../primitives/Tooltip";
4344
import { QueueName } from "../runs/v3/QueueName";
@@ -848,7 +849,7 @@ function HeaderCellContent({
848849
}}
849850
onMouseEnter={() => setIsFilterHovered(true)}
850851
onMouseLeave={() => setIsFilterHovered(false)}
851-
className="flex-shrink-0 rounded text-text-dimmed transition-colors hover:text-text-bright focus-custom"
852+
className="flex-shrink-0 rounded text-text-dimmed transition-colors focus-custom hover:text-text-bright"
852853
title="Toggle column filters"
853854
>
854855
{showFilters ? <IconFilter2X className="size-4" /> : <IconFilter2 className="size-4" />}
@@ -978,97 +979,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
978979

979980
// Empty state
980981
if (rows.length === 0) {
981-
return (
982-
<div
983-
className="h-full min-h-0 w-full overflow-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
984-
style={{ position: "relative" }}
985-
>
986-
<table style={{ display: "grid" }}>
987-
<thead
988-
className="border-t border-grid-bright bg-background-bright"
989-
style={{
990-
display: "grid",
991-
position: "sticky",
992-
top: 0,
993-
zIndex: 1,
994-
}}
995-
>
996-
{table.getHeaderGroups().map((headerGroup) => (
997-
<tr key={headerGroup.id} style={{ display: "flex", width: "100%" }}>
998-
{headerGroup.headers.map((header) => {
999-
const meta = header.column.columnDef.meta as ColumnMeta | undefined;
1000-
return (
1001-
<th
1002-
key={header.id}
1003-
className="group/header relative"
1004-
style={{
1005-
display: "flex",
1006-
width: header.getSize(),
1007-
}}
1008-
>
1009-
<HeaderCellContent
1010-
alignment={meta?.alignment ?? "left"}
1011-
tooltip={meta?.outputColumn.description}
1012-
onFilterClick={() => {
1013-
if (!showFilters) {
1014-
setFocusFilterColumn(header.id);
1015-
} else {
1016-
setColumnFilters([]);
1017-
}
1018-
setShowFilters(!showFilters);
1019-
}}
1020-
showFilters={showFilters}
1021-
hasActiveFilter={!!header.column.getFilterValue()}
1022-
sortDirection={header.column.getIsSorted()}
1023-
onSortClick={header.column.getToggleSortingHandler()}
1024-
canSort={header.column.getCanSort()}
1025-
>
1026-
{flexRender(header.column.columnDef.header, header.getContext())}
1027-
</HeaderCellContent>
1028-
{/* Column resizer */}
1029-
<div
1030-
onDoubleClick={() => header.column.resetSize()}
1031-
onMouseDown={header.getResizeHandler()}
1032-
onTouchStart={header.getResizeHandler()}
1033-
className={cn(
1034-
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
1035-
"opacity-0 group-hover/header:opacity-100",
1036-
"bg-charcoal-600 hover:bg-indigo-500",
1037-
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
1038-
)}
1039-
/>
1040-
</th>
1041-
);
1042-
})}
1043-
</tr>
1044-
))}
1045-
{/* Filter row - shown when filters are toggled */}
1046-
{showFilters && (
1047-
<tr style={{ display: "flex", width: "100%" }}>
1048-
{table.getHeaderGroups()[0]?.headers.map((header) => (
1049-
<FilterCell
1050-
key={`filter-${header.id}`}
1051-
column={header.column}
1052-
width={header.getSize()}
1053-
shouldFocus={focusFilterColumn === header.id}
1054-
onFocused={() => setFocusFilterColumn(null)}
1055-
/>
1056-
))}
1057-
</tr>
1058-
)}
1059-
</thead>
1060-
<tbody className="border-b border-grid-bright" style={{ display: "grid" }}>
1061-
<tr style={{ display: "flex" }}>
1062-
<td>
1063-
<Paragraph variant="extra-small" className="p-4 text-text-dimmed">
1064-
No results
1065-
</Paragraph>
1066-
</td>
1067-
</tr>
1068-
</tbody>
1069-
</table>
1070-
</div>
1071-
);
982+
return <ChartBlankState icon={IconTable} message="No data to display" />;
1072983
}
1073984

1074985
return (

apps/webapp/app/components/primitives/charts/BigNumberCard.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { OutputColumnMetadata } from "@internal/tsql";
2+
import { Hash } from "lucide-react";
23
import { useMemo } from "react";
34
import type {
45
BigNumberAggregationType,
56
BigNumberConfiguration,
67
} from "~/components/metrics/QueryWidget";
78
import { AnimatedNumber } from "../AnimatedNumber";
9+
import { ChartBlankState } from "./ChartBlankState";
810
import { Spinner } from "../Spinner";
9-
import { Paragraph } from "../Paragraph";
1011

1112
interface BigNumberCardProps {
1213
rows: Record<string, unknown>[];
@@ -138,13 +139,7 @@ export function BigNumberCard({ rows, columns, config, isLoading = false }: BigN
138139
}
139140

140141
if (result === null) {
141-
return (
142-
<div className="grid h-full place-items-center [container-type:size]">
143-
<Paragraph variant="small" className="text-text-dimmed">
144-
No data to display
145-
</Paragraph>
146-
</div>
147-
);
142+
return <ChartBlankState icon={Hash} message="No data to display" />;
148143
}
149144

150145
const { displayValue, unitSuffix, decimalPlaces } = abbreviate
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { cn } from "~/utils/cn";
2+
import { Paragraph } from "../Paragraph";
3+
4+
export function ChartBlankState({
5+
icon: Icon,
6+
message,
7+
className,
8+
}: {
9+
icon?: React.ComponentType<{ className?: string }>;
10+
message: string;
11+
className?: string;
12+
}) {
13+
return (
14+
<div className={cn("flex h-full w-full items-center justify-center", className)}>
15+
<div className="-mt-3 flex flex-col items-center gap-2">
16+
{Icon && <Icon className="size-12 text-charcoal-700" />}
17+
<Paragraph variant="small" className="text-text-dimmed/70">
18+
{message}
19+
</Paragraph>
20+
</div>
21+
</div>
22+
);
23+
}

0 commit comments

Comments
 (0)