Skip to content

Commit a06da56

Browse files
committed
table ui improvements
1 parent c8be2f8 commit a06da56

File tree

6 files changed

+295
-13
lines changed

6 files changed

+295
-13
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77

88
concurrency: ${{ github.workflow }}-${{ github.ref }}
99

10+
permissions:
11+
id-token: write # Required for OIDC
12+
1013
jobs:
1114
release:
1215
name: Release

packages/typegen/src/typegen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,11 @@ const generateTypedClientsSingle = async (
177177
apiKey: undefined,
178178
username:
179179
envNames?.auth && "username" in envNames.auth
180-
? envNames.auth.username ?? defaultEnvNames.username
180+
? (envNames.auth.username ?? defaultEnvNames.username)
181181
: defaultEnvNames.username,
182182
password:
183183
envNames?.auth && "password" in envNames.auth
184-
? envNames.auth.password ?? defaultEnvNames.password
184+
? (envNames.auth.password ?? defaultEnvNames.password)
185185
: defaultEnvNames.password,
186186
},
187187
db: envNames?.db ?? defaultEnvNames.db,

packages/typegen/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@tailwindcss/vite": "^4.1.18",
2727
"@tanstack/react-query": "^5.90.12",
2828
"@tanstack/react-table": "^8.21.3",
29+
"@tanstack/react-virtual": "^3.13.13",
2930
"@uidotdev/usehooks": "^2.4.1",
3031
"class-variance-authority": "^0.7.1",
3132
"clsx": "^2.1.1",

packages/typegen/web/src/components/MetadataFieldsDialog.tsx

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {
77
getSortedRowModel,
88
getFilteredRowModel,
99
type ColumnDef,
10+
flexRender,
1011
} from "@tanstack/react-table";
12+
import { useVirtualizer } from "@tanstack/react-virtual";
1113
import { DataGrid, DataGridContainer } from "./ui/data-grid";
12-
import { DataGridTable } from "./ui/data-grid-table";
1314
import { DataGridColumnHeader } from "./ui/data-grid-column-header";
14-
import { ScrollArea, ScrollBar } from "./ui/scroll-area";
1515
import { Input, InputWrapper } from "./ui/input";
1616
import { Switch } from "./ui/switch";
1717
import { Skeleton } from "./ui/skeleton";
@@ -131,6 +131,13 @@ export function MetadataFieldsDialog({
131131

132132
const [globalFilter, setGlobalFilter] = useState("");
133133

134+
// Reset search filter when dialog opens or table changes
135+
useEffect(() => {
136+
if (open) {
137+
setGlobalFilter("");
138+
}
139+
}, [open, tableName]);
140+
134141
// Get the config type to validate we're working with fmodata
135142
const configType = useWatch({
136143
control,
@@ -923,7 +930,7 @@ export function MetadataFieldsDialog({
923930
)}
924931
<span
925932
className={`font-medium ${
926-
row.isExcluded ? "text-muted-foreground line-through" : ""
933+
row.isExcluded ? "italic text-muted-foreground/50" : ""
927934
}`}
928935
>
929936
{info.getValue() as string}
@@ -1055,6 +1062,40 @@ export function MetadataFieldsDialog({
10551062
return fieldsData.filter((row) => !row.isExcluded).length;
10561063
}, [fieldsData]);
10571064

1065+
// Ref for the scrollable container
1066+
const tableContainerRef = useRef<HTMLDivElement>(null);
1067+
1068+
// Get filtered rows for virtualization
1069+
const { rows } = fieldsTable.getRowModel();
1070+
1071+
// Setup virtualizer
1072+
const rowVirtualizer = useVirtualizer({
1073+
count: rows.length,
1074+
getScrollElement: () => tableContainerRef.current,
1075+
estimateSize: () => 57, // Estimated row height in pixels
1076+
overscan: 10, // Number of items to render outside of the visible area
1077+
});
1078+
1079+
// Recalculate virtualizer when dialog opens or rows change
1080+
useEffect(() => {
1081+
if (open && tableContainerRef.current && rows.length > 0) {
1082+
// Small delay to ensure container is fully rendered
1083+
const timeoutId = setTimeout(() => {
1084+
rowVirtualizer.measure();
1085+
}, 0);
1086+
return () => clearTimeout(timeoutId);
1087+
}
1088+
}, [open, rows.length, rowVirtualizer]);
1089+
1090+
const virtualRows = rowVirtualizer.getVirtualItems();
1091+
const totalSize = rowVirtualizer.getTotalSize();
1092+
1093+
const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
1094+
const paddingBottom =
1095+
virtualRows.length > 0
1096+
? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0)
1097+
: 0;
1098+
10581099
return (
10591100
<Dialog open={open} onOpenChange={onOpenChange}>
10601101
<DialogContent
@@ -1100,11 +1141,126 @@ export function MetadataFieldsDialog({
11001141
emptyMessage="No fields found."
11011142
tableLayout={{ width: "auto", headerSticky: true }}
11021143
>
1103-
<DataGridContainer>
1104-
<ScrollArea className="max-h-[650px]">
1105-
<DataGridTable />
1106-
<ScrollBar orientation="horizontal" />
1107-
</ScrollArea>
1144+
<DataGridContainer border={true}>
1145+
<div
1146+
ref={tableContainerRef}
1147+
className="overflow-auto"
1148+
style={{
1149+
contain: "strict",
1150+
height: "650px",
1151+
maxHeight: "650px",
1152+
}}
1153+
>
1154+
<table className="w-full border-separate border-spacing-0">
1155+
<thead className="sticky top-0 z-10 bg-background">
1156+
{fieldsTable.getHeaderGroups().map((headerGroup) => (
1157+
<tr key={headerGroup.id}>
1158+
{headerGroup.headers.map((header) => (
1159+
<th
1160+
key={header.id}
1161+
className="h-10 px-4 text-left align-middle font-normal text-secondary-foreground/80 border-b"
1162+
style={{
1163+
width: header.getSize(),
1164+
}}
1165+
>
1166+
{header.isPlaceholder
1167+
? null
1168+
: flexRender(
1169+
header.column.columnDef.header,
1170+
header.getContext(),
1171+
)}
1172+
</th>
1173+
))}
1174+
</tr>
1175+
))}
1176+
</thead>
1177+
<tbody>
1178+
{isLoading ? (
1179+
<tr>
1180+
<td
1181+
colSpan={fieldsTable.getAllColumns().length}
1182+
className="text-center py-8"
1183+
>
1184+
<div className="text-muted-foreground">
1185+
Loading fields...
1186+
</div>
1187+
</td>
1188+
</tr>
1189+
) : rows.length === 0 ? (
1190+
<tr>
1191+
<td
1192+
colSpan={fieldsTable.getAllColumns().length}
1193+
className="text-center py-8"
1194+
>
1195+
<div className="text-muted-foreground">
1196+
No fields found.
1197+
</div>
1198+
</td>
1199+
</tr>
1200+
) : virtualRows.length === 0 && rows.length > 0 ? (
1201+
// Fallback: if virtualizer hasn't initialized yet, show all rows
1202+
rows.map((row) => (
1203+
<tr
1204+
key={row.id}
1205+
className="hover:bg-muted/40 border-b"
1206+
>
1207+
{row.getVisibleCells().map((cell) => (
1208+
<td
1209+
key={cell.id}
1210+
className="px-4 py-3 align-middle"
1211+
>
1212+
{flexRender(
1213+
cell.column.columnDef.cell,
1214+
cell.getContext(),
1215+
)}
1216+
</td>
1217+
))}
1218+
</tr>
1219+
))
1220+
) : (
1221+
<>
1222+
{paddingTop > 0 && (
1223+
<tr>
1224+
<td
1225+
colSpan={fieldsTable.getAllColumns().length}
1226+
style={{ height: `${paddingTop}px` }}
1227+
/>
1228+
</tr>
1229+
)}
1230+
{virtualRows.map((virtualRow) => {
1231+
const row = rows[virtualRow.index];
1232+
return (
1233+
<tr
1234+
key={row.id}
1235+
className="hover:bg-muted/40 border-b"
1236+
>
1237+
{row.getVisibleCells().map((cell) => (
1238+
<td
1239+
key={cell.id}
1240+
className="px-4 py-3 align-middle"
1241+
>
1242+
{flexRender(
1243+
cell.column.columnDef.cell,
1244+
cell.getContext(),
1245+
)}
1246+
</td>
1247+
))}
1248+
</tr>
1249+
);
1250+
})}
1251+
{paddingBottom > 0 && (
1252+
<tr>
1253+
<td
1254+
colSpan={fieldsTable.getAllColumns().length}
1255+
style={{ height: `${paddingBottom}px` }}
1256+
/>
1257+
</tr>
1258+
)}
1259+
</>
1260+
)}
1261+
</tbody>
1262+
</table>
1263+
</div>
11081264
</DataGridContainer>
11091265
</DataGrid>
11101266
)}

packages/typegen/web/src/components/MetadataTablesEditor.tsx

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ScrollArea, ScrollBar } from "./ui/scroll-area";
2323
import { Skeleton } from "./ui/skeleton";
2424
import { Badge } from "./ui/badge";
2525
import { DropdownMenuItem } from "./ui/dropdown-menu";
26+
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
2627

2728
interface MetadataTablesEditorProps {
2829
configIndex: number;
@@ -138,6 +139,72 @@ function FieldCountCell({
138139
return <span className="text-sm">{fieldCount}</span>;
139140
}
140141

142+
// Helper component to fetch and display relationship count for a table
143+
function RelationshipCountCell({
144+
tableName,
145+
isIncluded,
146+
configIndex,
147+
}: {
148+
tableName: string;
149+
isIncluded: boolean;
150+
configIndex: number;
151+
}) {
152+
const { data: parsedMetadata, isLoading } = useTableMetadata(
153+
configIndex,
154+
tableName,
155+
isIncluded, // Only fetch when table is included
156+
);
157+
158+
const relationships = useMemo(() => {
159+
if (!parsedMetadata?.entitySets || !parsedMetadata?.entityTypes) {
160+
return [];
161+
}
162+
163+
const entitySet = Object.values(parsedMetadata.entitySets).find(
164+
(es) => es.Name === tableName,
165+
);
166+
if (!entitySet) return [];
167+
168+
const entityType = parsedMetadata.entityTypes[entitySet.EntityType];
169+
if (!entityType?.NavigationProperties) return [];
170+
171+
const navProps = entityType.NavigationProperties;
172+
// Handle both Array and other formats
173+
if (Array.isArray(navProps)) {
174+
return navProps.map((np) => np.Name);
175+
}
176+
return [];
177+
}, [parsedMetadata, tableName]);
178+
179+
if (isLoading) {
180+
return <Skeleton className="w-12 h-5" />;
181+
}
182+
183+
if (!isIncluded) {
184+
return null;
185+
}
186+
187+
const count = relationships.length;
188+
189+
if (count === 0) {
190+
return <span className="text-muted-foreground">-</span>;
191+
}
192+
193+
const relationshipNames = relationships.join(", ");
194+
195+
return (
196+
<Tooltip>
197+
<TooltipTrigger asChild>
198+
<span className="text-sm cursor-help">{count}</span>
199+
</TooltipTrigger>
200+
<TooltipContent className="max-w-xs">
201+
<div className="font-medium mb-1">Relationships:</div>
202+
<div className="text-xs">{relationshipNames}</div>
203+
</TooltipContent>
204+
</Tooltip>
205+
);
206+
}
207+
141208
export function MetadataTablesEditor({
142209
configIndex,
143210
}: MetadataTablesEditorProps) {
@@ -358,12 +425,15 @@ export function MetadataTablesEditor({
358425
<DataGridColumnHeader column={column} title="Table Occurrence Name" />
359426
),
360427
enableSorting: true,
428+
// Use a large size relative to other columns so it takes most space
429+
// In fixed layout, space is distributed proportionally
430+
361431
cell: (info) => {
362432
const row = info.row.original;
363433
return (
364434
<span
365435
className={`font-medium ${
366-
!row.isIncluded ? "text-muted-foreground" : ""
436+
!row.isIncluded ? "italic text-muted-foreground" : ""
367437
}`}
368438
>
369439
{info.getValue() as string}
@@ -380,7 +450,9 @@ export function MetadataTablesEditor({
380450
<DataGridColumnHeader column={column} title="Fields" />
381451
),
382452
enableSorting: false,
383-
size: 100,
453+
size: 50,
454+
minSize: 50,
455+
maxSize: 100,
384456
cell: (info) => {
385457
const row = info.row.original;
386458
if (!row.isIncluded) {
@@ -398,6 +470,32 @@ export function MetadataTablesEditor({
398470
skeleton: <Skeleton className="w-12 h-5" />,
399471
},
400472
},
473+
{
474+
id: "relationships",
475+
header: ({ column }) => (
476+
<DataGridColumnHeader column={column} title="Relationships" />
477+
),
478+
enableSorting: false,
479+
size: 50,
480+
minSize: 10,
481+
maxSize: 100,
482+
cell: (info) => {
483+
const row = info.row.original;
484+
if (!row.isIncluded) {
485+
return null;
486+
}
487+
return (
488+
<RelationshipCountCell
489+
tableName={row.tableName}
490+
isIncluded={row.isIncluded}
491+
configIndex={configIndex}
492+
/>
493+
);
494+
},
495+
meta: {
496+
skeleton: <Skeleton className="w-12 h-5" />,
497+
},
498+
},
401499
{
402500
id: "actions",
403501
header: () => null,
@@ -600,7 +698,11 @@ export function MetadataTablesEditor({
600698
recordCount={tablesTable.getFilteredRowModel().rows.length}
601699
isLoading={isLoadingTables}
602700
emptyMessage="No tables found."
603-
tableLayout={{ width: "fixed", headerSticky: true }}
701+
tableLayout={{
702+
width: "auto",
703+
headerSticky: true,
704+
dense: true,
705+
}}
604706
>
605707
<DataGridContainer>
606708
<ScrollArea className="max-h-[650px]">

0 commit comments

Comments
 (0)