Skip to content

Instantly share code, notes, and snippets.

@minaairsupport
Created September 25, 2023 18:10
Show Gist options
  • Save minaairsupport/98fd235a189ea11b2223a6d540bbbfc8 to your computer and use it in GitHub Desktop.
Save minaairsupport/98fd235a189ea11b2223a6d540bbbfc8 to your computer and use it in GitHub Desktop.
import { useDispatch, useSelector } from 'react-redux';
import { operations } from 'ducks/shared/Multiselect';
import { toggleArrayValue } from 'utils/helpers';
const useDynamicSelectAll = ({ currentPageIds, selectors, context }) => {
const dispatch = useDispatch();
const selectedIds = useSelector(selectors.getSelectedIds);
const selectionState = useSelector(selectors.getSelectionState);
const isAllPagesSelected = useSelector(selectors.getIsAllSelected);
const isSelected = id => isAllPagesSelected || selectedIds.includes(id);
const countSelectedOnPage = currentPageIds.filter(isSelected).length;
const isHalfChecked = countSelectedOnPage > 0 && countSelectedOnPage < currentPageIds.length;
const isAllSelected = isAllPagesSelected || countSelectedOnPage === currentPageIds.length;
const deselectAll = () => dispatch(operations.selectIds(context)([]));
const forceClearSelectionState = () => dispatch(operations.forceClearSelectionState(context)());
const toggleAll = () => {
if (isAllPagesSelected) {
// Clear entire selection
dispatch(operations.selectIds(context)([]));
} else if (isHalfChecked || isAllSelected) {
// At least one id on this page is selected, deselect all on the page
const newIds = selectedIds.filter(id => !currentPageIds.includes(id));
dispatch(operations.selectIds(context)(newIds));
} else {
// Nothing on the page is selected, add whole page to selection
dispatch(
operations.selectIds(context)([...selectedIds, ...currentPageIds]),
);
}
};
const toggleSelection = (id) => {
const currentIds = isAllPagesSelected ? currentPageIds : [...selectedIds];
const newIds = toggleArrayValue(currentIds, id);
dispatch(operations.selectIds(context)(newIds));
};
const toggleSelectionInventory = (selectAllPage, toggledNodes) => {
if (isAllPagesSelected && selectAllPage && toggledNodes.length === 0) return;
let ids = [];
if (selectAllPage && toggledNodes.length === 0) {
const newIds = currentPageIds.filter(id => !selectedIds.includes(id));
ids = [...selectedIds, ...newIds];
} else if (!selectAllPage && toggledNodes.length === 0) {
const newIds = selectedIds.filter(id => !currentPageIds.includes(id));
ids = newIds;
} else if (selectAllPage && toggledNodes.length > 0) {
let newIds = [];
if (isAllPagesSelected) {
newIds = currentPageIds.filter(id => !toggledNodes.includes(id));
} else {
newIds = selectedIds.filter(id => !toggledNodes.includes(id));
}
ids = newIds;
} else if (!selectAllPage && toggledNodes.length > 0) {
const newIds = toggledNodes.filter(id => !selectedIds.includes(id));
ids = [...selectedIds, ...newIds];
}
dispatch(operations.selectIds(context)(ids));
};
const saveSelectionState = (pageNumber, currentSelectionState) => {
dispatch(
operations.setSelectionState(context)(pageNumber, currentSelectionState),
);
};
return {
toggleAll,
toggleSelection,
toggleSelectionInventory,
isSelected,
isHalfChecked,
isAllSelected,
deselectAll,
saveSelectionState,
selectionState,
isAllPagesSelected,
forceClearSelectionState,
};
};
export default useDynamicSelectAll;
import { RefObject, useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
GetContextMenuItemsParams,
IServerSideGetRowsRequest,
RowClickedEvent,
SortModelItem,
StatusPanelDef,
SideBarDef,
ToolPanelVisibleChangedEvent,
ColumnMovedEvent,
ColumnState,
SelectionChangedEvent,
PaginationChangedEvent,
IServerSideSelectionState,
} from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';
import { debounce } from 'lodash';
import { useLocation } from 'react-router-dom';
import { selectors as globalSelectors } from 'ducks/global';
import * as duckSelectors from 'ducks/selectors';
import { operations as searchOperations } from 'ducks/shared/Search';
import { operations as modalOperations } from 'ducks/Modal';
import { operations as toolOperations } from 'ducks/Tool';
import { EDIT_TOOL } from 'constants/modalTypes';
import usePermissions from 'shared/hooks/usePermissions';
import useDynamicSelectAll from 'shared/hooks/multiselect/useDynamicSelectAll';
import { loadToolsV2 } from 'ducks/Tool/operations';
import { loadLocationTools } from 'ducks/Location/operations';
import {
customColumnSideBar,
getCustomeContextMenuItems,
getDefaultStatus,
} from 'components/Table/utils';
import { defaultExportBody, defaultSort } from 'constants/tool';
import EmptySearchResult from 'shared/EmptySearchResult';
import { maniplateInventoryData } from 'utils/helpers';
import { trackEvent } from '@sbd-ctg/user-behavior-tracking';
const isToolPanelShowingInventoryKey = 'isToolPanelShowingInventoryKey';
interface UseNewToolsListProps {
selectors: any;
context: any;
assetsLocation?: any;
}
const LOCAL_COLUMN_STATE_KEY = 'LOCAL_COLUMN_STATE_KEY';
export default function useNewToolsList({
selectors,
context,
assetsLocation,
}: UseNewToolsListProps) {
const [gridRef, setGridRef] = useState<RefObject<AgGridReact>>();
const location = useLocation();
const keyField = 'thingId';
const tools: any[] = useSelector(selectors.getCurrentPageOfTools);
const currentPage: number = useSelector(selectors.getPageNumber);
const querySearch = useSelector(selectors.getToolQuery || selectors.getQuery);
const setQuery = searchOperations.setSearchQuery(context);
const pageSize: number = useSelector(selectors.getPageSize);
const inventorySwitch: boolean = useSelector(
(state: any) => state.tool.search.inventorySwitch
);
const IsInventory = context === 'TOOL';
const clearFilters = useSelector(
(state: any) => state.tool.search.clearFilters
);
const deselectSwitch = useSelector(
(state: any) => state.tool.search.deselectSwitch
);
let columnState = useSelector(selectors.getColumnState);
const isToolPanelShowing = localStorage.getItem(
`${context}_${isToolPanelShowingInventoryKey}`
);
const companyId = useSelector((state: any) =>
globalSelectors.getSelectedCompanyId(state)
);
const columns = useSelector(duckSelectors.getColumnsV2);
const setSearchPageSizeWithContext =
searchOperations.setSearchPageSize(context);
const setColumnStateWithContext = searchOperations.setColumnState(context)(
`${context}_${LOCAL_COLUMN_STATE_KEY}`
);
const dispatch = useDispatch();
const onToolClick = (id: string) =>
dispatch(modalOperations.showModal(EDIT_TOOL, { id }));
const setSearchPageSize = (size: number) =>
dispatch(setSearchPageSizeWithContext(size));
const setColumnState = (newState: any) =>
dispatch(setColumnStateWithContext(newState));
const setSearchPage = (page: any) =>
dispatch(searchOperations.setSearchPage(context)(page));
const exportAll = () =>
dispatch(toolOperations.exportTools(defaultExportBody));
if (
!columnState ||
(Array.isArray(columnState) && columnState.length === 0)
) {
columnState = JSON.parse(
localStorage.getItem(`${context}_${LOCAL_COLUMN_STATE_KEY}`) || '[]'
);
}
const { canManageAssets } = usePermissions();
const onRowClick = (id: string) => {
trackEvent('edit_tool', { category: 'Inventory' });
onToolClick(id);
};
const onPageSizeChange = (size: number) => {
trackEvent('page_size_change', { category: 'Inventory' });
setSearchPageSize(size);
};
function getFilterConditions(filterModel: any) {
const filterKeys = Object.keys(filterModel);
return filterKeys.includes('-')
? [{ field: 'search', contains: filterModel['-'].filter }]
: filterKeys.map((filter: string) => ({
field: filter,
contains: filterModel[filter].filter,
}));
}
function getOrderColumns(sortModel: SortModelItem[]) {
return sortModel.length > 0
? sortModel.map(({ sort, colId }) => ({
column: colId,
direction: sort,
}))
: [defaultSort];
}
const sideBar = useMemo<SideBarDef>(() => customColumnSideBar(), []);
useEffect(() => {
if (isToolPanelShowing === 'true') {
gridRef?.current?.api.openToolPanel('customColumns');
} else {
gridRef?.current?.api.closeToolPanel();
}
}, [gridRef, isToolPanelShowing]);
async function loadToolsWithRetry(
searchBody: any,
retries = 3,
retryDelay = 1000
): Promise<any> {
try {
let response;
const categoryImageListCall = await dispatch(
toolOperations.loadCategoryImages() as any
);
const categoryImageList = categoryImageListCall.payload.data.data;
const { locationId } = assetsLocation || {};
if (locationId && context === 'LOCATION') {
response = await dispatch(
loadLocationTools(searchBody, locationId) as any
);
return {
rowData: maniplateInventoryData(
response.payload.data.data.searchResults.things,
categoryImageList
),
rowCount: response.payload.data.data.searchResults.searchTotalTools,
};
}
response = await dispatch(loadToolsV2(context)(searchBody) as any);
return {
rowData: maniplateInventoryData(
response.payload.data.data.things,
categoryImageList
),
rowCount: response.payload.data.data.searchTotalTools,
};
} catch (error) {
if (retries > 0) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
return loadToolsWithRetry(searchBody, retries - 1, retryDelay * 2);
}
throw new Error(
`Failed to load data after ${retries} retries with error ${error}`
);
}
}
const loadData = (
params: IServerSideGetRowsRequest,
localPageSize: number = pageSize
) => {
const { endRow, filterModel, sortModel } = params;
const orderColumns = getOrderColumns(sortModel);
const filterConditions = getFilterConditions(filterModel);
const pageNumber = Math.round((endRow || pageSize) / localPageSize);
const searchBody = {
pageSize: localPageSize,
orderColumns,
filterConditions: filterConditions || [],
pageNumber, // used for redux
};
if (orderColumns.length && orderColumns[0].column !== 'thing.createdAt') {
trackEvent('sort_by_column', {
category: 'Inventory',
order_by: orderColumns[0].column,
direction: orderColumns[0].direction,
});
}
if (filterConditions.length) {
const [first] = filterConditions;
trackEvent('filter_by_column', {
category: 'Inventory',
field: first.field,
search_value: first.contains,
});
}
setSearchPage(pageNumber);
dispatch(searchOperations.setSearchFiltersOnly(context)(filterConditions));
return loadToolsWithRetry(searchBody);
};
const {
toggleSelectionInventory,
deselectAll,
saveSelectionState,
selectionState,
isAllPagesSelected,
forceClearSelectionState,
} = useDynamicSelectAll({
currentPageIds: tools ? tools.map((t) => t.thingId) : [],
selectors,
context,
});
useEffect(() => {
if (isAllPagesSelected) {
gridRef?.current?.api.selectAll();
}
}, [isAllPagesSelected]);
const onSelectionChanged = useCallback(
(event: SelectionChangedEvent) => {
const serverSelectionState: any = event.api.getServerSideSelectionState();
if (
(isAllPagesSelected &&
serverSelectionState?.selectAll &&
serverSelectionState?.toggledNodes.length > 0) ||
(isAllPagesSelected &&
!serverSelectionState?.selectAll &&
serverSelectionState?.toggledNodes.length === 0)
) {
forceClearSelectionState();
}
toggleSelectionInventory(
serverSelectionState?.selectAll,
serverSelectionState?.toggledNodes
);
saveSelectionState(currentPage, serverSelectionState);
},
[gridRef, toggleSelectionInventory]
);
useEffect(() => {
if (gridRef) {
const filterModel: any = {};
if (location.state && (location.state as any).categoryName) {
filterModel['category.categoryName'] = {
filter: (location.state as any).categoryName,
type: 'contains',
};
}
setTimeout(() => {
if (filterModel) {
gridRef.current?.api?.setFilterModel(filterModel);
}
}, 0);
}
// component cleanup
return () => {
window.history.replaceState(null, '', window.location.pathname);
dispatch(setQuery(''));
};
}, [gridRef]);
const getContextMenuItems = useCallback(
(params: GetContextMenuItemsParams) =>
getCustomeContextMenuItems(params, exportAll),
[]
);
const onPageSizeChanged = useCallback((value: any) => {
onPageSizeChange(Number(value));
}, []);
const statusBar = useMemo<{
statusPanels: StatusPanelDef[];
}>(() => getDefaultStatus(pageSize, onPageSizeChanged), [pageSize]);
useEffect(() => {
const debouncedSearch = debounce(() => {
gridRef?.current?.api?.setFilterModel({
'-': { filter: querySearch },
});
}, 1000);
debouncedSearch();
return () => debouncedSearch.cancel();
}, [querySearch]);
useEffect(() => {
gridRef?.current?.api?.refreshServerSide({ purge: true });
gridRef?.current?.api?.deselectAll();
}, [inventorySwitch, companyId]);
useEffect(() => {
gridRef?.current?.api.deselectAll();
deselectAll();
}, [deselectSwitch]);
useEffect(() => {
gridRef?.current?.api.setFilterModel(null);
}, [clearFilters]);
const onToolPanelVisibleChanged = useCallback(
(event: ToolPanelVisibleChangedEvent) => {
const isToolPanelShowingValue = event.api.isToolPanelShowing();
const actionColumnsSelector = isToolPanelShowingValue ? 'show' : 'hide';
trackEvent(`${actionColumnsSelector}_columns_selector`, {
category: 'Inventory',
});
localStorage.setItem(
`${context}_${isToolPanelShowingInventoryKey}`,
isToolPanelShowingValue.toString()
);
},
[]
);
const onColumnMoved = useCallback((event: ColumnMovedEvent) => {
const currentColumnOrder = event.columnApi
.getColumnState()
.map((col: ColumnState) => col.colId);
const selectionColumnIndex = currentColumnOrder.indexOf('selection');
if (selectionColumnIndex !== 0) {
event.columnApi.moveColumnByIndex(selectionColumnIndex, 0);
}
}, []);
const defaultSelectionState: IServerSideSelectionState = {
selectAll: false,
toggledNodes: [],
};
const onPaginationChanged = (params: PaginationChangedEvent) => {
const page = params.api.paginationGetCurrentPage() + 1;
setSearchPage(page);
if (!isAllPagesSelected) {
const selectionStateForPage =
(selectionState as any)?.[page] || defaultSelectionState;
params.api.setServerSideSelectionState(selectionStateForPage);
}
};
const tableProps = {
loadData,
setColumnState,
columnState,
keyField,
pageSize,
onPageSizeChange,
...(canManageAssets && { rowSelection: 'multiple' }),
exportAll,
setGridRef,
className: IsInventory ? 'sbd-ag-theme' : '',
/* Override table props */
onRowClicked: (event: RowClickedEvent) => onRowClick(event.data[keyField]),
columnDefs: columns,
onSelectionChanged,
getContextMenuItems,
statusBar,
suppressPaginationPanel: true,
noRowsOverlayComponent: EmptySearchResult,
sideBar,
suppressRowClickSelection: true,
onToolPanelVisibleChanged,
menuTabs: ['filterMenuTab', 'columnsMenuTab'],
rowHeight: 70,
onColumnMoved,
onPaginationChanged,
};
return tableProps;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment