feat: apply full width automatically for DataTable (#18357)
* feat: apply full width automatically for DataTable * change implementation * load all columns of insights routing table at the same time * update team member list * sticky columns for >= sm * fix type error --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
1 parent
c10c87c98e
commit
c415b2d7f5
11 files changed
+230
-64
No files matched your search
@@ -7,11 +7,12 @@ import { useVirtualizer, type Virtualizer } from "@tanstack/react-virtual";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useMemo, useEffect, memo } from "react";
|
||||
import { useEffect, memo } from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { Icon, TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui";
|
||||
|
||||
import { useColumnSizingVars } from "../hooks";
|
||||
import { usePersistentColumnResizing } from "../lib/resizing";
|
||||
|
||||
export type DataTableProps<TData, TValue> = {
|
||||
@@ -40,8 +41,8 @@ export function DataTable<TData, TValue>({
|
||||
enableColumnResizing,
|
||||
...rest
|
||||
}: DataTableProps<TData, TValue> & React.ComponentPropsWithoutRef<"div">) {
|
||||
const pathname = usePathname();
|
||||
const identifier = _identifier ?? pathname;
|
||||
const pathname = usePathname() as string | null;
|
||||
const identifier = _identifier ?? pathname ?? undefined;
|
||||
|
||||
const { rows } = table.getRowModel();
|
||||
|
||||
@@ -71,26 +72,13 @@ export function DataTable<TData, TValue>({
|
||||
}
|
||||
}, [rowVirtualizer.getVirtualItems().length, rows.length, tableContainerRef.current]);
|
||||
|
||||
const columnSizeVars = useMemo(() => {
|
||||
const headers = table.getFlatHeaders();
|
||||
const colSizes: { [key: string]: string } = {};
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const header = headers[i]!;
|
||||
const isAutoWidth = header.column.columnDef.meta?.autoWidth;
|
||||
colSizes[`--header-${kebabCase(header.id)}-size`] = isAutoWidth ? "auto" : `${header.getSize()}px`;
|
||||
colSizes[`--col-${kebabCase(header.column.id)}-size`] = isAutoWidth
|
||||
? "auto"
|
||||
: `${header.column.getSize()}px`;
|
||||
}
|
||||
return colSizes;
|
||||
}, [table.getFlatHeaders(), table.getState().columnSizingInfo, table.getState().columnSizing]);
|
||||
const columnSizingVars = useColumnSizingVars({ table });
|
||||
|
||||
usePersistentColumnResizing({
|
||||
enabled: Boolean(enableColumnResizing),
|
||||
table,
|
||||
// TODO: Figure out why 'identifier' somehow types to string | null
|
||||
identifier: identifier || undefined,
|
||||
tableContainerRef,
|
||||
identifier,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -113,7 +101,7 @@ export function DataTable<TData, TValue>({
|
||||
<TableNew
|
||||
className="grid border-0"
|
||||
style={{
|
||||
...columnSizeVars,
|
||||
...columnSizingVars,
|
||||
...(Boolean(enableColumnResizing) && { width: table.getTotalSize() }),
|
||||
}}>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
@@ -132,7 +120,7 @@ export function DataTable<TData, TValue>({
|
||||
className={classNames(
|
||||
"bg-subtle hover:bg-muted relative flex shrink-0 items-center",
|
||||
header.column.getCanSort() ? "cursor-pointer select-none" : "",
|
||||
meta?.sticky && "sticky top-0 z-20"
|
||||
meta?.sticky && "top-0 z-20 sm:sticky"
|
||||
)}>
|
||||
<div
|
||||
className="flex h-full w-full items-center overflow-hidden"
|
||||
@@ -262,7 +250,7 @@ function DataTableBody<TData>({
|
||||
"flex shrink-0 items-center overflow-hidden",
|
||||
variant === "compact" && "p-1.5",
|
||||
meta?.sticky &&
|
||||
"bg-default group-hover:!bg-muted group-data-[state=selected]:bg-subtle sticky"
|
||||
"bg-default group-hover:!bg-muted group-data-[state=selected]:bg-subtle sm:sticky"
|
||||
)}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
|
||||
@@ -2,7 +2,6 @@ export { DataTableToolbar } from "./DataTableToolbar";
|
||||
export { DataTableSelectionBar } from "./DataTableSelectionBar";
|
||||
export { DataTablePagination } from "./DataTablePagination";
|
||||
export { DataTableFilters } from "./filters";
|
||||
export { useFetchMoreOnBottomReached } from "./useFetchMoreOnBottomReached";
|
||||
export { DataTable } from "./DataTable";
|
||||
export { DataTableSkeleton } from "./DataTableSkeleton";
|
||||
export type { DataTableProps } from "./DataTable";
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./useDebouncedWidth";
|
||||
export * from "./useFetchMoreOnBottomReached";
|
||||
export * from "./useColumnSizingVars";
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Table } from "@tanstack/react-table";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const useColumnSizingVars = <TData>({ table }: { table: Table<TData> }) => {
|
||||
const headers = table.getFlatHeaders();
|
||||
const columnSizingInfo = table.getState().columnSizingInfo;
|
||||
const columnSizing = table.getState().columnSizing;
|
||||
|
||||
return useMemo(() => {
|
||||
const headers = table.getFlatHeaders();
|
||||
const colSizes: { [key: string]: string } = {};
|
||||
headers.forEach((header) => {
|
||||
const isAutoWidth = header.column.columnDef.meta?.autoWidth;
|
||||
colSizes[`--header-${kebabCase(header.id)}-size`] = isAutoWidth ? "auto" : `${header.getSize()}px`;
|
||||
colSizes[`--col-${kebabCase(header.column.id)}-size`] = isAutoWidth
|
||||
? "auto"
|
||||
: `${header.column.getSize()}px`;
|
||||
});
|
||||
return colSizes;
|
||||
// `columnSizing` and `columnSizingInfo` are not used in the memo,
|
||||
// but they're included in the deps to ensure the memo is re-evaluated when they change.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [headers, columnSizingInfo, columnSizing]);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import debounce from "lodash/debounce";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export function useDebouncedWidth(ref: React.RefObject<HTMLDivElement>, debounceMs = 100) {
|
||||
const [width, setWidth] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
|
||||
const element = ref.current;
|
||||
setWidth(element.clientWidth);
|
||||
|
||||
const debouncedSetWidth = debounce((width: number) => {
|
||||
setWidth(width);
|
||||
}, debounceMs);
|
||||
|
||||
const resizeObserver = new ResizeObserver(([entry]) => {
|
||||
debouncedSetWidth(entry.target.clientWidth);
|
||||
});
|
||||
|
||||
resizeObserver.observe(element);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
debouncedSetWidth.cancel();
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return width;
|
||||
}
|
||||
File renamed without changes.
@@ -2,4 +2,4 @@ export * from "./components";
|
||||
export * from "./lib/types";
|
||||
export * from "./lib/utils";
|
||||
export * from "./lib/context";
|
||||
export * from "./lib/resizing";
|
||||
export * from "./hooks";
|
||||
@@ -1,25 +1,27 @@
|
||||
import type { Table, ColumnSizingState } from "@tanstack/react-table";
|
||||
import type { Header, Table, ColumnSizingState } from "@tanstack/react-table";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import debounce from "lodash/debounce";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
import { useDebouncedWidth } from "../hooks";
|
||||
|
||||
type UsePersistentColumnResizingProps<TData> = {
|
||||
enabled: boolean;
|
||||
table: Table<TData>;
|
||||
identifier?: string;
|
||||
tableContainerRef: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
function getLocalStorageKey(identifier: string) {
|
||||
return `data-table-column-sizing-${identifier}`;
|
||||
}
|
||||
|
||||
function loadColumnSizing(identifier: string) {
|
||||
function loadColumnSizing(identifier: string): ColumnSizingState {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(getLocalStorageKey(identifier)) || "{}");
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function saveColumnSizing(identifier: string, columnSizing: ColumnSizingState) {
|
||||
@@ -28,12 +30,76 @@ function saveColumnSizing(identifier: string, columnSizing: ColumnSizingState) {
|
||||
|
||||
const debouncedSaveColumnSizing = debounce(saveColumnSizing, 1000);
|
||||
|
||||
function getAdjustedColumnSizing<TData>({
|
||||
headers,
|
||||
containerWidth,
|
||||
initialColumnSizing,
|
||||
currentColumnSizing,
|
||||
resizedColumns,
|
||||
}: {
|
||||
headers: Header<TData, unknown>[];
|
||||
containerWidth: number;
|
||||
initialColumnSizing: ColumnSizingState;
|
||||
currentColumnSizing: ColumnSizingState;
|
||||
resizedColumns: Set<string>;
|
||||
}) {
|
||||
// Return early if any column has auto width
|
||||
const hasAutoWidthColumn = headers.some((header) => header.column.columnDef.meta?.autoWidth);
|
||||
if (hasAutoWidthColumn) {
|
||||
return currentColumnSizing;
|
||||
}
|
||||
|
||||
const getColumnSize = (id: string) =>
|
||||
resizedColumns.has(id) ? currentColumnSizing[id] : initialColumnSizing[id];
|
||||
|
||||
const isAdjustable = (header: Header<TData, unknown>) =>
|
||||
header.column.columnDef.enableResizing !== false && !resizedColumns.has(header.id);
|
||||
|
||||
// Calculate how many columns can be adjusted
|
||||
const numberOfAdjustableColumns = headers.filter(isAdjustable).length;
|
||||
|
||||
// Calculate widths and required adjustments
|
||||
const totalColumnsWidth = headers.reduce((total, header) => total + getColumnSize(header.id), 0);
|
||||
const widthToFill = Math.max(0, containerWidth - totalColumnsWidth);
|
||||
const widthPerAdjustableColumn =
|
||||
numberOfAdjustableColumns === 0 ? 0 : Math.floor(widthToFill / numberOfAdjustableColumns);
|
||||
|
||||
// Handle any leftover width (due to rounding)
|
||||
const leftoverWidth = widthToFill - widthPerAdjustableColumn * numberOfAdjustableColumns;
|
||||
|
||||
// Build the new column sizing object
|
||||
const newColumnSizing = headers.reduce((acc, header, index) => {
|
||||
const baseWidth = getColumnSize(header.id);
|
||||
const adjustmentWidth = isAdjustable(header) ? widthPerAdjustableColumn : 0;
|
||||
const isLastColumn = index === headers.length - 1;
|
||||
const extraWidth = isLastColumn ? leftoverWidth : 0;
|
||||
|
||||
acc[header.id] = baseWidth + adjustmentWidth + extraWidth;
|
||||
return acc;
|
||||
}, {} as ColumnSizingState);
|
||||
|
||||
return newColumnSizing;
|
||||
}
|
||||
|
||||
function getPartialColumnSizing(columnSizing: ColumnSizingState, columnsToExtract: Set<string>) {
|
||||
return Object.keys(columnSizing).reduce((acc, key) => {
|
||||
if (columnsToExtract.has(key)) {
|
||||
acc[key] = columnSizing[key];
|
||||
}
|
||||
return acc;
|
||||
}, {} as ColumnSizingState);
|
||||
}
|
||||
|
||||
export function usePersistentColumnResizing<TData>({
|
||||
enabled,
|
||||
table,
|
||||
identifier,
|
||||
tableContainerRef,
|
||||
}: UsePersistentColumnResizingProps<TData>) {
|
||||
const [_, setColumnSizing] = useState<ColumnSizingState>({});
|
||||
const initialized = useRef(false);
|
||||
const columnSizing = useRef<ColumnSizingState>({});
|
||||
const initialColumnSizing = useRef<ColumnSizingState>({});
|
||||
const resizedColumns = useRef<Set<string>>(new Set());
|
||||
|
||||
const onColumnSizingChange = useCallback(
|
||||
(updater: ColumnSizingState | ((old: ColumnSizingState) => ColumnSizingState)) => {
|
||||
@@ -41,10 +107,24 @@ export function usePersistentColumnResizing<TData>({
|
||||
// but TS doesn't know that, and this won't happen.
|
||||
if (!identifier) return;
|
||||
|
||||
const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn;
|
||||
if (isResizingColumn) {
|
||||
resizedColumns.current.add(isResizingColumn);
|
||||
}
|
||||
table.setState((oldTableState) => {
|
||||
const newColumnSizing = typeof updater === "function" ? updater(oldTableState.columnSizing) : updater;
|
||||
debouncedSaveColumnSizing(identifier, newColumnSizing);
|
||||
setColumnSizing(newColumnSizing);
|
||||
let newColumnSizing = typeof updater === "function" ? updater(oldTableState.columnSizing) : updater;
|
||||
newColumnSizing = getAdjustedColumnSizing({
|
||||
headers: table.getFlatHeaders(),
|
||||
containerWidth: tableContainerRef.current?.clientWidth || 0,
|
||||
initialColumnSizing: initialColumnSizing.current,
|
||||
currentColumnSizing: newColumnSizing,
|
||||
resizedColumns: resizedColumns.current,
|
||||
});
|
||||
debouncedSaveColumnSizing(
|
||||
identifier,
|
||||
getPartialColumnSizing(newColumnSizing, resizedColumns.current)
|
||||
);
|
||||
columnSizing.current = newColumnSizing;
|
||||
|
||||
return {
|
||||
...oldTableState,
|
||||
@@ -55,19 +135,50 @@ export function usePersistentColumnResizing<TData>({
|
||||
[identifier, table]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !identifier) return;
|
||||
const debouncedContainerWidth = useDebouncedWidth(tableContainerRef);
|
||||
|
||||
const newColumnSizing = loadColumnSizing(identifier);
|
||||
setColumnSizing(newColumnSizing);
|
||||
useEffect(() => {
|
||||
if (!enabled || !identifier || !initialized.current) return;
|
||||
const newColumnSizing = getAdjustedColumnSizing({
|
||||
headers: table.getFlatHeaders(),
|
||||
containerWidth: debouncedContainerWidth,
|
||||
initialColumnSizing: initialColumnSizing.current,
|
||||
currentColumnSizing: columnSizing.current,
|
||||
resizedColumns: resizedColumns.current,
|
||||
});
|
||||
|
||||
columnSizing.current = newColumnSizing;
|
||||
table.setState((old) => ({
|
||||
...old,
|
||||
columnSizing: newColumnSizing,
|
||||
}));
|
||||
}, [debouncedContainerWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !identifier) return;
|
||||
|
||||
// loadedColumnSizing is a partial object of explicitly resized columns.
|
||||
const loadedColumnSizing = loadColumnSizing(identifier);
|
||||
|
||||
// combine loaded sizes and the default sizes from TanStack Table
|
||||
initialColumnSizing.current = table.getFlatHeaders().reduce((acc, header) => {
|
||||
acc[header.id] = loadedColumnSizing[header.id] || header.getSize();
|
||||
return acc;
|
||||
}, {} as ColumnSizingState);
|
||||
|
||||
columnSizing.current = loadedColumnSizing;
|
||||
resizedColumns.current = new Set(Object.keys(loadedColumnSizing));
|
||||
|
||||
table.setState((old) => ({
|
||||
...old,
|
||||
columnSizing: loadedColumnSizing,
|
||||
}));
|
||||
table.setOptions((prev) => ({
|
||||
...prev,
|
||||
columnResizeMode: "onChange",
|
||||
onColumnSizingChange,
|
||||
}));
|
||||
|
||||
initialized.current = true;
|
||||
}, [enabled, identifier, table, onColumnSizingChange]);
|
||||
}
|
||||
@@ -295,6 +295,7 @@ function MemberListContent(props: Props) {
|
||||
id: "select",
|
||||
enableHiding: false,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
size: 30,
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
@@ -402,7 +403,8 @@ function MemberListContent(props: Props) {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
size: 80,
|
||||
size: 90,
|
||||
enableResizing: false,
|
||||
meta: {
|
||||
sticky: { position: "right" },
|
||||
},
|
||||
@@ -659,6 +661,7 @@ function MemberListContent(props: Props) {
|
||||
table={table}
|
||||
tableContainerRef={tableContainerRef}
|
||||
isPending={isPending}
|
||||
enableColumnResizing={true}
|
||||
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}>
|
||||
<DataTableToolbar.Root>
|
||||
<div className="flex w-full gap-2">
|
||||
|
||||
@@ -282,18 +282,21 @@ export function RoutingFormResponsesTableContent({
|
||||
|
||||
const columnFilters = useColumnFilters();
|
||||
|
||||
const { data: headers, isLoading: isHeadersLoading } =
|
||||
trpc.viewer.insights.routingFormResponsesHeaders.useQuery(
|
||||
{
|
||||
userId: selectedUserId ?? undefined,
|
||||
teamId: selectedTeamId ?? undefined,
|
||||
isAll: isAll ?? false,
|
||||
routingFormId: selectedRoutingFormId ?? undefined,
|
||||
},
|
||||
{
|
||||
enabled: initialConfigIsReady,
|
||||
}
|
||||
);
|
||||
const {
|
||||
data: headers,
|
||||
isLoading: isHeadersLoading,
|
||||
isSuccess: isHeadersSuccess,
|
||||
} = trpc.viewer.insights.routingFormResponsesHeaders.useQuery(
|
||||
{
|
||||
userId: selectedUserId ?? undefined,
|
||||
teamId: selectedTeamId ?? undefined,
|
||||
isAll: isAll ?? false,
|
||||
routingFormId: selectedRoutingFormId ?? undefined,
|
||||
},
|
||||
{
|
||||
enabled: initialConfigIsReady,
|
||||
}
|
||||
);
|
||||
|
||||
const { data, fetchNextPage, isFetching, hasNextPage, isLoading } =
|
||||
trpc.viewer.insights.routingFormResponses.useInfiniteQuery(
|
||||
@@ -323,7 +326,7 @@ export function RoutingFormResponsesTableContent({
|
||||
const totalFetched = flatData.length;
|
||||
|
||||
const processedData = useMemo(() => {
|
||||
if (isHeadersLoading) return [];
|
||||
if (!isHeadersSuccess) return [];
|
||||
return flatData.map((response) => {
|
||||
const row: RoutingFormTableRow = {
|
||||
id: response.id,
|
||||
@@ -363,7 +366,7 @@ export function RoutingFormResponsesTableContent({
|
||||
|
||||
return row;
|
||||
});
|
||||
}, [flatData, headers, isHeadersLoading]);
|
||||
}, [flatData, headers, isHeadersSuccess]);
|
||||
|
||||
const statusOrder: Record<BookingStatus, number> = {
|
||||
[BookingStatus.ACCEPTED]: 1,
|
||||
@@ -375,8 +378,12 @@ export function RoutingFormResponsesTableContent({
|
||||
|
||||
const columnHelper = createColumnHelper<RoutingFormTableRow>();
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
const columns = useMemo(() => {
|
||||
if (!isHeadersSuccess) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
columnHelper.accessor("routedToBooking", {
|
||||
id: "bookedBy",
|
||||
header: t("routing_form_insights_booked_by"),
|
||||
@@ -412,7 +419,6 @@ export function RoutingFormResponsesTableContent({
|
||||
return columnHelper.accessor(fieldHeader.id, {
|
||||
id: fieldHeader.id,
|
||||
header: convertToTitleCase(fieldHeader.label),
|
||||
size: 200,
|
||||
cell: (info) => {
|
||||
const values = info.getValue();
|
||||
if (isMultiSelect) {
|
||||
@@ -456,7 +462,6 @@ export function RoutingFormResponsesTableContent({
|
||||
columnHelper.accessor("routedToBooking", {
|
||||
id: "bookingStatus",
|
||||
header: t("routing_form_insights_booking_status"),
|
||||
size: 250,
|
||||
cell: (info) => (
|
||||
<div className="max-w-[250px]">
|
||||
<BookingStatusBadge booking={info.getValue()} />
|
||||
@@ -480,7 +485,6 @@ export function RoutingFormResponsesTableContent({
|
||||
columnHelper.accessor("routedToBooking", {
|
||||
id: "bookingAt",
|
||||
header: t("routing_form_insights_booking_at"),
|
||||
size: 250,
|
||||
enableColumnFilter: false,
|
||||
cell: (info) => (
|
||||
<div className="max-w-[250px]">
|
||||
@@ -496,7 +500,6 @@ export function RoutingFormResponsesTableContent({
|
||||
columnHelper.accessor("routedToBooking", {
|
||||
id: "assignmentReason",
|
||||
header: t("routing_form_insights_assignment_reason"),
|
||||
size: 250,
|
||||
meta: {
|
||||
filter: { type: "text" },
|
||||
},
|
||||
@@ -515,7 +518,6 @@ export function RoutingFormResponsesTableContent({
|
||||
columnHelper.accessor("createdAt", {
|
||||
id: "submittedAt",
|
||||
header: t("routing_form_insights_submitted_at"),
|
||||
size: 250,
|
||||
enableColumnFilter: false,
|
||||
cell: (info) => (
|
||||
<div className="whitespace-nowrap">
|
||||
@@ -523,9 +525,8 @@ export function RoutingFormResponsesTableContent({
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
],
|
||||
[headers, t, copyToClipboard]
|
||||
);
|
||||
];
|
||||
}, [isHeadersSuccess, headers, t, copyToClipboard]);
|
||||
|
||||
const table = useReactTable<RoutingFormTableRow>({
|
||||
data: processedData,
|
||||
@@ -534,7 +535,7 @@ export function RoutingFormResponsesTableContent({
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
defaultColumn: {
|
||||
size: 200,
|
||||
size: 150,
|
||||
},
|
||||
state: {
|
||||
columnFilters,
|
||||
|
||||
@@ -118,7 +118,7 @@ function UserListTableContent() {
|
||||
const { data: session } = useSession();
|
||||
const { isPlatformUser } = useGetUserAttributes();
|
||||
const { data: org } = trpc.viewer.organizations.listCurrent.useQuery();
|
||||
const { data: attributes } = trpc.viewer.attributes.list.useQuery();
|
||||
const { data: attributes, isSuccess: isSuccessAttributes } = trpc.viewer.attributes.list.useQuery();
|
||||
const { data: teams } = trpc.viewer.organizations.getTeams.useQuery();
|
||||
const { data: facetedTeamValues } = trpc.viewer.organizations.getFacetedValues.useQuery();
|
||||
|
||||
@@ -163,7 +163,6 @@ function UserListTableContent() {
|
||||
|
||||
//we must flatten the array of arrays from the useInfiniteQuery hook
|
||||
const flatData = useMemo(() => data?.pages?.flatMap((page) => page.rows) ?? [], [data]) as UserTableUser[];
|
||||
const totalFetched = flatData.length;
|
||||
|
||||
const memorisedColumns = useMemo(() => {
|
||||
const permissions = {
|
||||
@@ -543,6 +542,11 @@ function UserListTableContent() {
|
||||
}
|
||||
};
|
||||
|
||||
if (!isSuccessAttributes) {
|
||||
// do not render the table until the attributes are fetched
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable
|
||||
|
||||
Reference in new issue
Block a user