Select all matching hosts (#1226)
* #940 passing matching props to table; typescript * #940 fixed use of results title * #940 logic to swap states of selected and not * #940 optimized rendering selected text * #940 began styling to match flexbox spacing * #940 added logic to API * #940 clean up * #940 fixed action * #940 fixed conflict and coment * #940 hide select matching if under 100 hosts * #940 fixed issue with status filter
This commit is contained in:
@@ -5,6 +5,8 @@ import { noop } from "lodash";
|
||||
// @ts-ignore
|
||||
import DataTable from "./DataTable";
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 100;
|
||||
|
||||
describe("DataTable - component", () => {
|
||||
it("renders a data table based on the columns and data passed in", () => {
|
||||
const columns = [
|
||||
@@ -24,6 +26,10 @@ describe("DataTable - component", () => {
|
||||
data={data}
|
||||
isLoading={false}
|
||||
onSort={noop}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
resultsTitle="users"
|
||||
defaultPageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -61,6 +67,10 @@ describe("DataTable - component", () => {
|
||||
sortDirection={"desc"}
|
||||
isLoading={false}
|
||||
onSort={noop}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
resultsTitle="users"
|
||||
defaultPageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -82,6 +92,10 @@ describe("DataTable - component", () => {
|
||||
sortDirection={"desc"}
|
||||
isLoading={false}
|
||||
onSort={noop}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
resultsTitle="users"
|
||||
defaultPageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -111,6 +125,10 @@ describe("DataTable - component", () => {
|
||||
sortDirection={"desc"}
|
||||
isLoading={false}
|
||||
onSort={noop}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
resultsTitle="users"
|
||||
defaultPageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -129,6 +147,10 @@ describe("DataTable - component", () => {
|
||||
sortDirection={"asc"}
|
||||
isLoading={false}
|
||||
onSort={noop}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
resultsTitle="users"
|
||||
defaultPageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
+95
-24
@@ -1,25 +1,44 @@
|
||||
import React, { useMemo, useEffect, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTable, useSortBy, useRowSelect } from "react-table";
|
||||
import useDeepEffect from "utilities/hooks/useDeepEffect";
|
||||
|
||||
import Spinner from "components/loaders/Spinner";
|
||||
import Button from "../../buttons/Button";
|
||||
|
||||
const baseClass = "data-table-container";
|
||||
|
||||
interface IDataTableProps {
|
||||
columns: any;
|
||||
data: any;
|
||||
isLoading: boolean;
|
||||
sortHeader: any;
|
||||
sortDirection: any;
|
||||
onSort: any; // TODO: an event type
|
||||
onSelectActionClick: any; // TODO: an event type
|
||||
showMarkAllPages: boolean;
|
||||
isAllPagesSelected: boolean; // TODO: make dependent on showMarkAllPages
|
||||
toggleAllPagesSelected?: any; // TODO: an event type and make it dependent on showMarkAllPages
|
||||
resultsTitle: string;
|
||||
defaultPageSize: number;
|
||||
}
|
||||
|
||||
// This data table uses react-table for implementation. The relevant documentation of the library
|
||||
// can be found here https://react-table.tanstack.com/docs/api/useTable
|
||||
const DataTable = (props) => {
|
||||
const {
|
||||
columns: tableColumns,
|
||||
data: tableData,
|
||||
isLoading,
|
||||
sortHeader,
|
||||
sortDirection,
|
||||
onSort,
|
||||
onSelectActionClick,
|
||||
} = props;
|
||||
|
||||
const DataTable = ({
|
||||
columns: tableColumns,
|
||||
data: tableData,
|
||||
isLoading,
|
||||
sortHeader,
|
||||
sortDirection,
|
||||
onSort,
|
||||
onSelectActionClick,
|
||||
showMarkAllPages,
|
||||
isAllPagesSelected,
|
||||
toggleAllPagesSelected,
|
||||
resultsTitle,
|
||||
defaultPageSize,
|
||||
}: IDataTableProps) => {
|
||||
const columns = useMemo(() => {
|
||||
return tableColumns;
|
||||
}, [tableColumns]);
|
||||
@@ -35,6 +54,7 @@ const DataTable = (props) => {
|
||||
prepareRow,
|
||||
selectedFlatRows,
|
||||
toggleAllRowsSelected,
|
||||
isAllRowsSelected,
|
||||
state: tableState,
|
||||
} = useTable(
|
||||
{
|
||||
@@ -69,15 +89,55 @@ const DataTable = (props) => {
|
||||
}
|
||||
}, [sortBy, sortHeader, onSort, sortDirection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAllPagesSelected) {
|
||||
toggleAllRowsSelected(true);
|
||||
}
|
||||
}, [isAllPagesSelected]);
|
||||
|
||||
useDeepEffect(() => {
|
||||
if (
|
||||
Object.keys(selectedRowIds).length < rows.length &&
|
||||
toggleAllPagesSelected
|
||||
) {
|
||||
toggleAllPagesSelected(false);
|
||||
}
|
||||
}, [tableState.selectedRowIds, toggleAllPagesSelected]);
|
||||
|
||||
const onSelectActionButtonClick = useCallback(() => {
|
||||
const entityIds = selectedFlatRows.map((row) => row.original.id);
|
||||
const entityIds = selectedFlatRows.map((row: any) => row.original.id);
|
||||
onSelectActionClick(entityIds);
|
||||
}, [onSelectActionClick, selectedFlatRows]);
|
||||
|
||||
const onToggleAllPagesClick = useCallback(() => {
|
||||
toggleAllPagesSelected();
|
||||
}, [toggleAllPagesSelected]);
|
||||
|
||||
const onClearSelectionClick = useCallback(() => {
|
||||
toggleAllRowsSelected(false);
|
||||
toggleAllPagesSelected(false);
|
||||
}, [toggleAllRowsSelected]);
|
||||
|
||||
const renderSelectedText = (): JSX.Element => {
|
||||
if (isAllPagesSelected) {
|
||||
return <p>All matching {resultsTitle} are selected</p>;
|
||||
}
|
||||
|
||||
if (isAllRowsSelected) {
|
||||
return <p>All {resultsTitle} on this page are selected</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<p>
|
||||
<span>{selectedFlatRows.length}</span> selected
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const shouldRenderToggleAllPages =
|
||||
Object.keys(selectedRowIds).length >= defaultPageSize &&
|
||||
showMarkAllPages &&
|
||||
!isAllPagesSelected;
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={"data-table data-table__wrapper"}>
|
||||
@@ -99,18 +159,29 @@ const DataTable = (props) => {
|
||||
</th>
|
||||
<th className={"active-selection__container"}>
|
||||
<div className={"active-selection__inner"}>
|
||||
<p>
|
||||
<span>{selectedFlatRows.length}</span> selected
|
||||
</p>
|
||||
<Button
|
||||
onClick={onClearSelectionClick}
|
||||
variant={"text-link"}
|
||||
>
|
||||
Clear selection
|
||||
</Button>
|
||||
<Button onClick={onSelectActionButtonClick}>
|
||||
Transfer to team
|
||||
</Button>
|
||||
<div className={"active-selection__inner-left"}>
|
||||
{renderSelectedText()}
|
||||
{shouldRenderToggleAllPages && (
|
||||
<Button
|
||||
onClick={onToggleAllPagesClick}
|
||||
variant={"text-link"}
|
||||
className={"light-text"}
|
||||
>
|
||||
<>Select all matching {resultsTitle}</>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={onClearSelectionClick}
|
||||
variant={"text-link"}
|
||||
>
|
||||
Clear selection
|
||||
</Button>
|
||||
</div>
|
||||
<div className={"active-selection__inner-right"}>
|
||||
<Button onClick={onSelectActionButtonClick}>
|
||||
Transfer to team
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@@ -56,6 +56,7 @@
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
width: 100%;
|
||||
max-width: calc( 100vw - 2rem - 2rem - 0.125rem - 300px); // same as wrapper page for flex layout
|
||||
border: 0;
|
||||
|
||||
&__container {
|
||||
@@ -64,6 +65,7 @@
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
@@ -79,6 +81,12 @@
|
||||
margin-right: $pad-medium;
|
||||
}
|
||||
}
|
||||
|
||||
&__inner-left,
|
||||
&__inner-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
|
||||
@@ -22,9 +22,9 @@ interface ITableQueryData {
|
||||
pageIndex: number;
|
||||
}
|
||||
|
||||
interface ITableContainerProps<T, U> {
|
||||
columns: T[];
|
||||
data: U[];
|
||||
interface ITableContainerProps {
|
||||
columns: any; // TODO: Figure out type
|
||||
data: any; // TODO: Figure out type
|
||||
isLoading: boolean;
|
||||
defaultSortHeader: string;
|
||||
defaultSortDirection: string;
|
||||
@@ -36,10 +36,13 @@ interface ITableContainerProps<T, U> {
|
||||
onSelectActionClick?: (selectedItemIds: number[]) => void;
|
||||
inputPlaceHolder: string;
|
||||
disableActionButton?: boolean;
|
||||
resultsTitle?: string;
|
||||
resultsTitle: string;
|
||||
additionalQueries?: string;
|
||||
emptyComponent: React.ElementType;
|
||||
className?: string;
|
||||
showMarkAllPages: boolean;
|
||||
isAllPagesSelected: boolean; // TODO: make dependent on showMarkAllPages
|
||||
toggleAllPagesSelected?: any; // TODO: an event type and make it dependent on showMarkAllPages
|
||||
}
|
||||
|
||||
const baseClass = "table-container";
|
||||
@@ -48,29 +51,28 @@ const DEFAULT_PAGE_SIZE = 100;
|
||||
const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEBOUNCE_QUERY_DELAY = 300;
|
||||
|
||||
const TableContainer = <T, U>(
|
||||
props: ITableContainerProps<T, U>
|
||||
): JSX.Element => {
|
||||
const {
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
defaultSortHeader,
|
||||
defaultSortDirection,
|
||||
onActionButtonClick,
|
||||
inputPlaceHolder,
|
||||
additionalQueries,
|
||||
onQueryChange,
|
||||
resultsTitle,
|
||||
emptyComponent,
|
||||
className,
|
||||
disableActionButton,
|
||||
actionButtonText,
|
||||
actionButtonIcon,
|
||||
actionButtonVariant,
|
||||
onSelectActionClick,
|
||||
} = props;
|
||||
|
||||
const TableContainer = ({
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
defaultSortHeader,
|
||||
defaultSortDirection,
|
||||
onActionButtonClick,
|
||||
inputPlaceHolder,
|
||||
additionalQueries,
|
||||
onQueryChange,
|
||||
resultsTitle,
|
||||
emptyComponent,
|
||||
className,
|
||||
disableActionButton,
|
||||
actionButtonText,
|
||||
actionButtonIcon,
|
||||
actionButtonVariant,
|
||||
onSelectActionClick,
|
||||
showMarkAllPages,
|
||||
isAllPagesSelected,
|
||||
toggleAllPagesSelected,
|
||||
}: ITableContainerProps): JSX.Element => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [sortHeader, setSortHeader] = useState(defaultSortHeader || "");
|
||||
const [sortDirection, setSortDirection] = useState(
|
||||
@@ -215,6 +217,11 @@ const TableContainer = <T, U>(
|
||||
sortDirection={sortDirection}
|
||||
onSort={onSortChange}
|
||||
onSelectActionClick={onSelectActionClick}
|
||||
showMarkAllPages={showMarkAllPages}
|
||||
isAllPagesSelected={isAllPagesSelected}
|
||||
toggleAllPagesSelected={toggleAllPagesSelected}
|
||||
resultsTitle={resultsTitle}
|
||||
defaultPageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
<Pagination
|
||||
resultsOnCurrentPage={data.length}
|
||||
|
||||
@@ -140,6 +140,10 @@ $base-class: "button";
|
||||
box-shadow: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.light-text {
|
||||
font-weight: $regular;
|
||||
}
|
||||
}
|
||||
|
||||
&--text-icon {
|
||||
|
||||
@@ -14,6 +14,7 @@ export default {
|
||||
FORGOT_PASSWORD: "/v1/fleet/forgot_password",
|
||||
HOSTS: "/v1/fleet/hosts",
|
||||
HOSTS_TRANSFER: "/v1/fleet/hosts/transfer",
|
||||
HOSTS_TRANSFER_BY_FILTER: "/v1/fleet/hosts/transfer/filter",
|
||||
INVITES: "/v1/fleet/invites",
|
||||
LABELS: "/v1/fleet/labels",
|
||||
LABEL_HOSTS: (id: number): string => {
|
||||
|
||||
@@ -92,5 +92,25 @@ export default (client: any) => {
|
||||
})
|
||||
);
|
||||
},
|
||||
transferToTeamByFilter: (
|
||||
teamId: number | null,
|
||||
query: string,
|
||||
status: string,
|
||||
labelId: number | null
|
||||
) => {
|
||||
const { HOSTS_TRANSFER_BY_FILTER } = endpoints;
|
||||
const endpoint = client._endpoint(HOSTS_TRANSFER_BY_FILTER);
|
||||
return client.authenticatedPost(
|
||||
endpoint,
|
||||
JSON.stringify({
|
||||
team_id: teamId,
|
||||
filters: {
|
||||
query,
|
||||
status,
|
||||
label_id: labelId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+2
@@ -248,6 +248,8 @@ const MembersPage = (props: IMembersPageProps): JSX.Element => {
|
||||
onQueryChange={onQueryChange}
|
||||
inputPlaceHolder={"Search"}
|
||||
emptyComponent={EmptyMembers}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
/>
|
||||
{showAddMemberModal ? (
|
||||
<AddMemberModal
|
||||
|
||||
@@ -186,6 +186,8 @@ const TeamManagementPage = (): JSX.Element => {
|
||||
onQueryChange={onQueryChange}
|
||||
resultsTitle={"teams"}
|
||||
emptyComponent={EmptyTeams}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
/>
|
||||
{showCreateTeamModal ? (
|
||||
<CreateTeamModal
|
||||
|
||||
@@ -592,7 +592,7 @@ export class UserManagementPage extends Component {
|
||||
actionButtonText={"Create User"}
|
||||
onActionButtonClick={toggleCreateUserModal}
|
||||
onQueryChange={onTableQueryChange}
|
||||
resultsTitle={"rows"}
|
||||
resultsTitle={"users"}
|
||||
emptyComponent={EmptyUsers}
|
||||
/>
|
||||
{renderCreateUserModal()}
|
||||
|
||||
@@ -100,6 +100,8 @@ export class ManageHostsPage extends PureComponent {
|
||||
? storedHiddenColumns
|
||||
: defaultHiddenColumns,
|
||||
selectedHostIds: [],
|
||||
isAllMatchingHostsSelected: false,
|
||||
searchQuery: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,6 +193,10 @@ export class ManageHostsPage extends PureComponent {
|
||||
if (sortHeader !== "") {
|
||||
sortBy = [{ id: sortHeader, direction: sortDirection }];
|
||||
}
|
||||
|
||||
// keep track as a local state to be used later
|
||||
this.setState({ searchQuery });
|
||||
|
||||
dispatch(
|
||||
getHosts(pageIndex, pageSize, selectedFilter, searchQuery, sortBy)
|
||||
);
|
||||
@@ -268,11 +274,35 @@ export class ManageHostsPage extends PureComponent {
|
||||
};
|
||||
|
||||
onTransferHostSubmit = (team) => {
|
||||
const { toggleTransferHostModal } = this;
|
||||
const { dispatch } = this.props;
|
||||
const { selectedHostIds } = this.state;
|
||||
const { toggleTransferHostModal, isAcceptableStatus } = this;
|
||||
const { dispatch, selectedFilter, selectedLabel } = this.props;
|
||||
const {
|
||||
selectedHostIds,
|
||||
isAllMatchingHostsSelected,
|
||||
searchQuery,
|
||||
} = this.state;
|
||||
const teamId = team.id === "no-team" ? null : team.id;
|
||||
dispatch(hostActions.transferToTeam(teamId, selectedHostIds))
|
||||
let action = hostActions.transferToTeam(teamId, selectedHostIds);
|
||||
|
||||
if (isAllMatchingHostsSelected) {
|
||||
let status = "";
|
||||
let labelId = null;
|
||||
|
||||
if (isAcceptableStatus(selectedFilter)) {
|
||||
status = selectedFilter;
|
||||
} else {
|
||||
labelId = selectedLabel.id;
|
||||
}
|
||||
|
||||
action = hostActions.transferToTeamByFilter(
|
||||
teamId,
|
||||
searchQuery,
|
||||
status,
|
||||
labelId
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(action)
|
||||
.then(() => {
|
||||
const successMessage =
|
||||
teamId === null
|
||||
@@ -286,8 +316,19 @@ export class ManageHostsPage extends PureComponent {
|
||||
renderFlash("error", "Could not transfer hosts. Please try again.")
|
||||
);
|
||||
});
|
||||
|
||||
toggleTransferHostModal();
|
||||
this.setState({ selectedHostIds: [] });
|
||||
this.setState({ isAllMatchingHostsSelected: false });
|
||||
};
|
||||
|
||||
isAcceptableStatus = (filter) => {
|
||||
return (
|
||||
filter === "new" ||
|
||||
filter === "online" ||
|
||||
filter === "offline" ||
|
||||
filter === "mia"
|
||||
);
|
||||
};
|
||||
|
||||
clearHostUpdates() {
|
||||
@@ -312,6 +353,19 @@ export class ManageHostsPage extends PureComponent {
|
||||
this.setState({ showTransferHostModal: !showTransferHostModal });
|
||||
};
|
||||
|
||||
toggleAllMatchingHosts = (shouldSelect = undefined) => {
|
||||
// shouldSelect?: boolean
|
||||
const { isAllMatchingHostsSelected } = this.state;
|
||||
|
||||
if (shouldSelect !== undefined) {
|
||||
this.setState({ isAllMatchingHostsSelected: shouldSelect });
|
||||
} else {
|
||||
this.setState({
|
||||
isAllMatchingHostsSelected: !isAllMatchingHostsSelected,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderEditColumnsModal = () => {
|
||||
const { config, currentUser } = this.props;
|
||||
const { showEditColumnsModal, hiddenColumns } = this.state;
|
||||
@@ -578,11 +632,12 @@ export class ManageHostsPage extends PureComponent {
|
||||
hosts,
|
||||
loadingHosts,
|
||||
} = this.props;
|
||||
const { hiddenColumns } = this.state;
|
||||
const { hiddenColumns, isAllMatchingHostsSelected } = this.state;
|
||||
const {
|
||||
onTableQueryChange,
|
||||
onEditColumnsClick,
|
||||
onTransferToTeamClick,
|
||||
toggleAllMatchingHosts,
|
||||
} = this;
|
||||
|
||||
// The data has not been fetched yet.
|
||||
@@ -615,6 +670,9 @@ export class ManageHostsPage extends PureComponent {
|
||||
onQueryChange={onTableQueryChange}
|
||||
resultsTitle={"hosts"}
|
||||
emptyComponent={EmptyHosts}
|
||||
showMarkAllPages
|
||||
isAllPagesSelected={isAllMatchingHostsSelected}
|
||||
toggleAllPagesSelected={toggleAllMatchingHosts}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,6 +41,27 @@ const transferToTeam = (teamId: number | null, hostIds: number[]): any => {
|
||||
};
|
||||
};
|
||||
|
||||
const transferToTeamByFilter = (
|
||||
teamId: number | null,
|
||||
query: string,
|
||||
status: string,
|
||||
labelId: number | null
|
||||
): any => {
|
||||
return (dispatch: any) => {
|
||||
dispatch(loadRequest());
|
||||
return Fleet.hosts
|
||||
.transferToTeamByFilter(teamId, query, status, labelId)
|
||||
.then(() => {
|
||||
dispatch(transferHostsSuccess());
|
||||
})
|
||||
.catch((res: IApiError) => {
|
||||
const errorsObject = formatErrorResponse(res);
|
||||
dispatch(transferHostsFailure(errorsObject));
|
||||
throw errorsObject;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const LOAD_PAGINATED = "LOAD_PAGINATED";
|
||||
export const loadPaginated = (): any => {
|
||||
return (dispatch: any) => {
|
||||
@@ -80,6 +101,7 @@ export const refetchHostStart = (host: IHost): any => {
|
||||
export default {
|
||||
...actions,
|
||||
transferToTeam,
|
||||
transferToTeamByFilter,
|
||||
refetchHostSuccess,
|
||||
refetchHostFailure,
|
||||
refetchHostStart,
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
UseColumnOrderInstanceProps,
|
||||
UseColumnOrderState,
|
||||
UseExpandedHooks,
|
||||
UseExpandedInstanceProps,
|
||||
UseExpandedOptions,
|
||||
UseExpandedRowProps,
|
||||
UseExpandedState,
|
||||
UseFiltersColumnOptions,
|
||||
UseFiltersColumnProps,
|
||||
UseFiltersInstanceProps,
|
||||
UseFiltersOptions,
|
||||
UseFiltersState,
|
||||
UseGlobalFiltersColumnOptions,
|
||||
UseGlobalFiltersInstanceProps,
|
||||
UseGlobalFiltersOptions,
|
||||
UseGlobalFiltersState,
|
||||
UseGroupByCellProps,
|
||||
UseGroupByColumnOptions,
|
||||
UseGroupByColumnProps,
|
||||
UseGroupByHooks,
|
||||
UseGroupByInstanceProps,
|
||||
UseGroupByOptions,
|
||||
UseGroupByRowProps,
|
||||
UseGroupByState,
|
||||
UsePaginationInstanceProps,
|
||||
UsePaginationOptions,
|
||||
UsePaginationState,
|
||||
UseResizeColumnsColumnOptions,
|
||||
UseResizeColumnsColumnProps,
|
||||
UseResizeColumnsOptions,
|
||||
UseResizeColumnsState,
|
||||
UseRowSelectHooks,
|
||||
UseRowSelectInstanceProps,
|
||||
UseRowSelectOptions,
|
||||
UseRowSelectRowProps,
|
||||
UseRowSelectState,
|
||||
UseRowStateCellProps,
|
||||
UseRowStateInstanceProps,
|
||||
UseRowStateOptions,
|
||||
UseRowStateRowProps,
|
||||
UseRowStateState,
|
||||
UseSortByColumnOptions,
|
||||
UseSortByColumnProps,
|
||||
UseSortByHooks,
|
||||
UseSortByInstanceProps,
|
||||
UseSortByOptions,
|
||||
UseSortByState,
|
||||
} from "react-table";
|
||||
|
||||
declare module "react-table" {
|
||||
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
|
||||
|
||||
export interface TableOptions<
|
||||
D extends Record<string, unknown>
|
||||
> extends UseExpandedOptions<D>,
|
||||
UseFiltersOptions<D>,
|
||||
UseGlobalFiltersOptions<D>,
|
||||
UseGroupByOptions<D>,
|
||||
UsePaginationOptions<D>,
|
||||
UseResizeColumnsOptions<D>,
|
||||
UseRowSelectOptions<D>,
|
||||
UseRowStateOptions<D>,
|
||||
UseSortByOptions<D>,
|
||||
// note that having Record here allows you to add anything to the options, this matches the spirit of the
|
||||
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
|
||||
// feature set, this is a safe default.
|
||||
Record<string, any> {}
|
||||
|
||||
export interface Hooks<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
> extends UseExpandedHooks<D>,
|
||||
UseGroupByHooks<D>,
|
||||
UseRowSelectHooks<D>,
|
||||
UseSortByHooks<D> {}
|
||||
|
||||
export interface TableInstance<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
> extends UseColumnOrderInstanceProps<D>,
|
||||
UseExpandedInstanceProps<D>,
|
||||
UseFiltersInstanceProps<D>,
|
||||
UseGlobalFiltersInstanceProps<D>,
|
||||
UseGroupByInstanceProps<D>,
|
||||
UsePaginationInstanceProps<D>,
|
||||
UseRowSelectInstanceProps<D>,
|
||||
UseRowStateInstanceProps<D>,
|
||||
UseSortByInstanceProps<D> {}
|
||||
|
||||
export interface TableState<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
> extends UseColumnOrderState<D>,
|
||||
UseExpandedState<D>,
|
||||
UseFiltersState<D>,
|
||||
UseGlobalFiltersState<D>,
|
||||
UseGroupByState<D>,
|
||||
UsePaginationState<D>,
|
||||
UseResizeColumnsState<D>,
|
||||
UseRowSelectState<D>,
|
||||
UseRowStateState<D>,
|
||||
UseSortByState<D> {}
|
||||
|
||||
export interface ColumnInterface<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
> extends UseFiltersColumnOptions<D>,
|
||||
UseGlobalFiltersColumnOptions<D>,
|
||||
UseGroupByColumnOptions<D>,
|
||||
UseResizeColumnsColumnOptions<D>,
|
||||
UseSortByColumnOptions<D> {}
|
||||
|
||||
export interface ColumnInstance<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
> extends UseFiltersColumnProps<D>,
|
||||
UseGroupByColumnProps<D>,
|
||||
UseResizeColumnsColumnProps<D>,
|
||||
UseSortByColumnProps<D> {}
|
||||
|
||||
export interface Cell<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
V = any
|
||||
> extends UseGroupByCellProps<D>,
|
||||
UseRowStateCellProps<D> {}
|
||||
|
||||
export interface Row<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
> extends UseExpandedRowProps<D>,
|
||||
UseGroupByRowProps<D>,
|
||||
UseRowSelectRowProps<D>,
|
||||
UseRowStateRowProps<D> {}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { isEqual } from "lodash";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fn Anonymous function passing into the hook
|
||||
* @param deps What dependencies to watch for changes
|
||||
*
|
||||
* Adapted from https://betterprogramming.pub/how-to-use-the-react-hook-usedeepeffect-815818c0ad9d,
|
||||
* this hook does a deeper check for changes within objects and arrays
|
||||
*/
|
||||
|
||||
const useDeepEffect = (fn: () => void, deps: Array<any>) => {
|
||||
const isFirst = useRef(true);
|
||||
const prevDeps = useRef(deps);
|
||||
|
||||
useEffect(() => {
|
||||
const isSame = prevDeps.current.every((obj, index) =>
|
||||
isEqual(obj, deps[index])
|
||||
);
|
||||
|
||||
if (isFirst.current || !isSame) {
|
||||
fn();
|
||||
}
|
||||
|
||||
isFirst.current = false;
|
||||
prevDeps.current = deps;
|
||||
}, [deps, fn]);
|
||||
};
|
||||
|
||||
export default useDeepEffect;
|
||||
Reference in New Issue
Block a user