From 4dd6d71affcd054be52478f61026f159a5971373 Mon Sep 17 00:00:00 2001 From: Guillermo Espinosa Date: Mon, 2 Sep 2024 19:31:21 -0400 Subject: [PATCH] useTableEntities hook --- packages/ui/src/DataTable/index.js | 309 ++++++++++-------- packages/ui/src/DataTable/utils/index.js | 4 +- .../src/DataTable/utils/useTableEntities.js | 41 +++ packages/ui/src/index.js | 2 +- 4 files changed, 225 insertions(+), 131 deletions(-) create mode 100644 packages/ui/src/DataTable/utils/useTableEntities.js diff --git a/packages/ui/src/DataTable/index.js b/packages/ui/src/DataTable/index.js index 97c97d82..c9938cb1 100644 --- a/packages/ui/src/DataTable/index.js +++ b/packages/ui/src/DataTable/index.js @@ -71,7 +71,10 @@ import { removeCleanRows, useDeepEqualMemo } from "./utils"; -import rowClick, { finalizeSelection } from "./utils/rowClick"; +import rowClick, { + changeSelectedEntities, + finalizeSelection +} from "./utils/rowClick"; import PagingTool from "./PagingTool"; import SearchBar from "./SearchBar"; import DisplayOptions from "./DisplayOptions"; @@ -404,7 +407,7 @@ const DataTable = ({ doNotValidateUntouchedRows, editingCellSelectAll, entities: __origEntities = [], - entitiesAcrossPages, + entitiesAcrossPages: _entitiesAcrossPages, entityCount, errorParsingUrlString, expandAllByDefault, @@ -490,6 +493,20 @@ const DataTable = ({ const entities = useDeepEqualMemo( (reduxFormEntities?.length ? reduxFormEntities : _origEntities) || [] ); + const entitiesAcrossPages = useDeepEqualMemo(_entitiesAcrossPages); + + // This is because we need to maintain the reduxFormSelectedEntityIdMap and + // allOrderedEntities updated + useEffect(() => { + change("allOrderedEntities", entitiesAcrossPages); + if (entities.length === 0 || isEmpty(reduxFormSelectedEntityIdMap)) return; + changeSelectedEntities({ + idMap: reduxFormSelectedEntityIdMap, + entities, + change + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [entitiesAcrossPages, reduxFormSelectedEntityIdMap]); const [tableConfig, setTableConfig] = useState({ fieldOptions: [] }); @@ -2436,24 +2453,31 @@ const DataTable = ({ return acc; }, []); - const filtersOnNonDisplayedFields = []; - if (filters && filters.length) { - schema.fields.forEach(field => { - const ccDisplayName = getCCDisplayName(field); - if (field.isHidden) { - filters.forEach(filter => { - if (filter.filterOn === ccDisplayName) { - filtersOnNonDisplayedFields.push({ - ...filter, - displayName: field.displayName - }); - } - }); - } - }); - } + const filtersOnNonDisplayedFields = useMemo(() => { + const _filtersOnNonDisplayedFields = []; + if (filters && filters.length) { + schema.fields.forEach(field => { + const ccDisplayName = getCCDisplayName(field); + if (field.isHidden) { + filters.forEach(filter => { + if (filter.filterOn === ccDisplayName) { + _filtersOnNonDisplayedFields.push({ + ...filter, + displayName: field.displayName + }); + } + }); + } + }); + } + return _filtersOnNonDisplayedFields; + }, [filters, schema.fields]); + const numRows = isInfinite ? entities.length : pageSize; - const idMap = reduxFormSelectedEntityIdMap || {}; + const idMap = useMemo( + () => reduxFormSelectedEntityIdMap || {}, + [reduxFormSelectedEntityIdMap] + ); const selectedRowCount = Object.keys(idMap).filter(key => idMap[key]).length; let rowsToShow = doNotShowEmptyRows @@ -2481,131 +2505,158 @@ const DataTable = ({ /> ); - let showSelectAll = false; - let showClearAll = false; - // we want to show select all if every row on the current page is selected - // and not every row across all pages are already selected. - if (!isInfinite) { - const canShowSelectAll = - withSelectAll || - (entitiesAcrossPages && numRows < entitiesAcrossPages.length); - if (canShowSelectAll) { - // could all be disabled - let atLeastOneRowOnCurrentPageSelected = false; - const allRowsOnCurrentPageSelected = entities.every(e => { - const rowId = getIdOrCodeOrIndex(e); - const selected = idMap[rowId] || isEntityDisabled(e); - if (selected) atLeastOneRowOnCurrentPageSelected = true; - return selected; - }); - if (atLeastOneRowOnCurrentPageSelected && allRowsOnCurrentPageSelected) { - let everyEntitySelected; - if (isLocalCall) { - everyEntitySelected = entitiesAcrossPages.every(e => { - const rowId = getIdOrCodeOrIndex(e); - return idMap[rowId] || isEntityDisabled(e); - }); - } else { - everyEntitySelected = entityCount <= selectedRowCount; - } - if (everyEntitySelected) { - showClearAll = selectedRowCount; + const { showSelectAll, showClearAll } = useMemo(() => { + let _showSelectAll = false; + let _showClearAll = false; + // we want to show select all if every row on the current page is selected + // and not every row across all pages are already selected. + if (!isInfinite) { + const canShowSelectAll = + withSelectAll || + (entitiesAcrossPages && numRows < entitiesAcrossPages.length); + if (canShowSelectAll) { + // could all be disabled + let atLeastOneRowOnCurrentPageSelected = false; + const allRowsOnCurrentPageSelected = entities.every(e => { + const rowId = getIdOrCodeOrIndex(e); + const selected = idMap[rowId] || isEntityDisabled(e); + if (selected) atLeastOneRowOnCurrentPageSelected = true; + return selected; + }); + if ( + atLeastOneRowOnCurrentPageSelected && + allRowsOnCurrentPageSelected + ) { + let everyEntitySelected; + if (isLocalCall) { + everyEntitySelected = entitiesAcrossPages.every(e => { + const rowId = getIdOrCodeOrIndex(e); + return idMap[rowId] || isEntityDisabled(e); + }); + } else { + everyEntitySelected = entityCount <= selectedRowCount; + } + if (everyEntitySelected) { + _showClearAll = selectedRowCount; + } + // only show if not all selected + _showSelectAll = !everyEntitySelected; } - // only show if not all selected - showSelectAll = !everyEntitySelected; } } - } + return { showSelectAll: _showSelectAll, showClearAll: _showClearAll }; + }, [ + entities, + entitiesAcrossPages, + entityCount, + idMap, + isEntityDisabled, + isInfinite, + isLocalCall, + numRows, + selectedRowCount, + withSelectAll + ]); const showNumSelected = !noSelect && !isSingleSelect && !hideSelectedCount; - let selectedAndTotalMessage = ""; - if (showNumSelected) { - selectedAndTotalMessage += `${selectedRowCount} Selected `; - } - if (showCount && showNumSelected) { - selectedAndTotalMessage += `/ `; - } - if (showCount) { - selectedAndTotalMessage += `${entityCount || 0} Total`; - } - if (selectedAndTotalMessage) { - selectedAndTotalMessage =
{selectedAndTotalMessage}
; - } + const selectedAndTotalMessage = useMemo(() => { + let _selectedAndTotalMessage = ""; + if (showNumSelected) { + _selectedAndTotalMessage += `${selectedRowCount} Selected `; + } + if (showCount && showNumSelected) { + _selectedAndTotalMessage += `/ `; + } + if (showCount) { + _selectedAndTotalMessage += `${entityCount || 0} Total`; + } + if (_selectedAndTotalMessage) { + _selectedAndTotalMessage =
{_selectedAndTotalMessage}
; + } + return _selectedAndTotalMessage; + }, [entityCount, selectedRowCount, showCount, showNumSelected]); const shouldShowPaging = !isInfinite && withPaging && (hidePageSizeWhenPossible ? entityCount > pageSize : true); - let SubComponentToUse; - if (SubComponent) { - SubComponentToUse = row => { - let shouldShow = true; - if (shouldShowSubComponent) { - shouldShow = shouldShowSubComponent(row.original); - } - if (shouldShow) { - return SubComponent(row); - } - }; - } - let nonDisplayedFilterComp; - if (filtersOnNonDisplayedFields.length) { - const content = filtersOnNonDisplayedFields.map( - ({ displayName, path, selectedFilter, filterValue }) => { - let filterValToDisplay = filterValue; - if (selectedFilter === "inList") { - filterValToDisplay = Array.isArray(filterValToDisplay) - ? filterValToDisplay - : filterValToDisplay && filterValToDisplay.split(";"); + const SubComponentToUse = useMemo(() => { + if (SubComponent) { + return row => { + let shouldShow = true; + if (shouldShowSubComponent) { + shouldShow = shouldShowSubComponent(row.original); } - if (Array.isArray(filterValToDisplay)) { - filterValToDisplay = filterValToDisplay.join(", "); + if (shouldShow) { + return SubComponent(row); } - return ( -
- {displayName || startCase(camelCase(path))}{" "} - {lowerCase(selectedFilter)} {filterValToDisplay} -
- ); - } - ); - nonDisplayedFilterComp = ( -
- - Active filters on hidden columns: -
-
- {content} -
+ }; + } + return; + }, [SubComponent, shouldShowSubComponent]); + + const nonDisplayedFilterComp = useMemo(() => { + if (filtersOnNonDisplayedFields.length) { + const content = filtersOnNonDisplayedFields.map( + ({ displayName, path, selectedFilter, filterValue }) => { + let filterValToDisplay = filterValue; + if (selectedFilter === "inList") { + filterValToDisplay = Array.isArray(filterValToDisplay) + ? filterValToDisplay + : filterValToDisplay && filterValToDisplay.split(";"); } - > - - - - ); - } - let filteredEnts = entities; - - if (onlyShowRowsWErrors) { - const rowToErrorMap = {}; - forEach(reduxFormCellValidation, (err, cellId) => { - if (err) { - const [rowId] = cellId.split(":"); - rowToErrorMap[rowId] = true; - } - }); - filteredEnts = entities.filter(e => { - return rowToErrorMap[e.id]; - }); - } + if (Array.isArray(filterValToDisplay)) { + filterValToDisplay = filterValToDisplay.join(", "); + } + return ( +
+ {displayName || startCase(camelCase(path))}{" "} + {lowerCase(selectedFilter)} {filterValToDisplay} +
+ ); + } + ); + return ( +
+ + Active filters on hidden columns: +
+
+ {content} +
+ } + > + + + + ); + } + return null; + }, [filtersOnNonDisplayedFields]); + + const filteredEnts = useMemo(() => { + if (onlyShowRowsWErrors) { + const rowToErrorMap = {}; + forEach(reduxFormCellValidation, (err, cellId) => { + if (err) { + const [rowId] = cellId.split(":"); + rowToErrorMap[rowId] = true; + } + }); + return entities.filter(e => { + return rowToErrorMap[e.id]; + }); + } + return entities; + }, [entities, onlyShowRowsWErrors, reduxFormCellValidation]); - // We are nnot rerendering when props and change are changed, + // We are not rerendering when props and change are changed, // we need to figure out how to manage them correctly const renderColumns = useMemo( () => diff --git a/packages/ui/src/DataTable/utils/index.js b/packages/ui/src/DataTable/utils/index.js index cd5b9570..257028a7 100644 --- a/packages/ui/src/DataTable/utils/index.js +++ b/packages/ui/src/DataTable/utils/index.js @@ -25,6 +25,7 @@ import { isBottomRightCornerOfRectangle } from "./isBottomRightCornerOfRectangle import { handleCopyTable } from "./handleCopyTable"; import { PRIMARY_SELECTED_VAL } from "./primarySelectedValue"; import { useDeepEqualMemo } from "./useDeepEqualMemo"; +import { useTableEntities } from "./useTableEntities"; export { defaultParsePaste, @@ -51,5 +52,6 @@ export { PRIMARY_SELECTED_VAL, removeCleanRows, stripNumberAtEnd, - useDeepEqualMemo + useDeepEqualMemo, + useTableEntities }; diff --git a/packages/ui/src/DataTable/utils/useTableEntities.js b/packages/ui/src/DataTable/utils/useTableEntities.js new file mode 100644 index 00000000..1e6566f0 --- /dev/null +++ b/packages/ui/src/DataTable/utils/useTableEntities.js @@ -0,0 +1,41 @@ +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { change, initialize } from "redux-form"; + +export const useTableEntities = tableFormName => { + const dispatch = useDispatch(); + const selectTableEntities = useCallback( + (entities = []) => { + initialize(tableFormName, {}, true, { + keepDirty: true, + updateUnregisteredFields: true, + keepValues: true + }); + const selectedEntityIdMap = {}; + entities.forEach(entity => { + selectedEntityIdMap[entity.id] = { + entity, + time: Date.now() + }; + }); + dispatch( + change( + tableFormName, + "reduxFormSelectedEntityIdMap", + selectedEntityIdMap + ) + ); + }, + [dispatch, tableFormName] + ); + + const { allOrderedEntities = [], selectedEntities = {} } = useSelector( + state => ({ + allOrderedEntities: + state.form?.[tableFormName]?.values?.allOrderedEntities, + selectedEntities: + state.form?.[tableFormName]?.values?.reduxFormSelectedEntityIdMap + }) + ); + return { selectTableEntities, allOrderedEntities, selectedEntities }; +}; diff --git a/packages/ui/src/index.js b/packages/ui/src/index.js index f03a8ff9..bfc35d11 100644 --- a/packages/ui/src/index.js +++ b/packages/ui/src/index.js @@ -20,7 +20,7 @@ export { default as DataTable, ConnectedPagingTool as PagingTool } from "./DataTable"; -export { removeCleanRows } from "./DataTable/utils"; +export { removeCleanRows, useTableEntities } from "./DataTable/utils"; export { getIdOrCodeOrIndex, useDeepEqualMemo } from "./DataTable/utils"; export { default as convertSchema } from "./DataTable/utils/convertSchema";