UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

300 lines (299 loc) 12.4 kB
import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; import { useAdaptable } from '../AdaptableContext'; import usePrevious from '../../components/utils/usePrevious'; import { DataChangeHistoryModuleId } from '../../Utilities/Constants/ModuleConstants'; import { buildActionColumnButton } from './buildActionColumnButton'; import { AdaptableAgGrid } from '../../agGrid/AdaptableAgGrid'; export const DataChangeHistoryGrid = (props) => { const { changeHistoryLog, adaptableContainerId, agGridContainerId, onUndoChange, onClearRow } = props; const mainAdaptableInstance = useAdaptable(); const [_adaptableApi, setAdaptableApi] = useState(null); const adaptableApiRef = useRef(null); adaptableApiRef.current = _adaptableApi; const undoChangeEnabled = mainAdaptableInstance.api.entitlementApi.isModuleFullEntitlement(DataChangeHistoryModuleId); const previousChangeHistoryLog = usePrevious(changeHistoryLog, changeHistoryLog); // initialize Adaptable grid useEffect(() => { const initializeAdaptableGrid = async () => { const gridOptions = buildGridOptions(mainAdaptableInstance, changeHistoryLog); const adaptableOptions = buildAdaptableOptions(mainAdaptableInstance, adaptableContainerId, agGridContainerId, undoChangeEnabled, onUndoChange, onClearRow); const modules = mainAdaptableInstance.getAgGridRegisteredModules(); const dataChangeHistoryAdaptableApi = await AdaptableAgGrid._initInternal({ variant: 'vanilla', adaptableOptions, gridOptions, modules, }); setAdaptableApi(dataChangeHistoryAdaptableApi); }; initializeAdaptableGrid(); return () => { requestAnimationFrame(() => { adaptableApiRef.current?.internalApi .getAdaptableInstance() .destroy({ unmount: true, destroyAgGrid: true }); }); }; }, []); // update row data useEffect(() => { const adaptableApi = adaptableApiRef.current; if (!adaptableApi) { // initial render return; } const addedChangeKeys = Object.keys(changeHistoryLog).filter((newChangeKey) => { const previousChangeWithSameKey = previousChangeHistoryLog[newChangeKey]; if (!previousChangeWithSameKey) { return true; } const addedChange = changeHistoryLog[newChangeKey]; return addedChange.changedAt !== previousChangeWithSameKey.changedAt; }); const removedChangeKeys = Object.keys(previousChangeHistoryLog).filter((previousChangeKey) => { const newChangeWithSameKey = changeHistoryLog[previousChangeKey]; if (!newChangeWithSameKey) { return true; } const previousChange = previousChangeHistoryLog[previousChangeKey]; return previousChange.changedAt !== newChangeWithSameKey.changedAt; }); const addedChangeHistoryLogs = filterChangeHistoryLog(changeHistoryLog, addedChangeKeys); const removedChangeHistoryLogs = filterChangeHistoryLog(previousChangeHistoryLog, removedChangeKeys); // here we access directly the AG Grid API, but this is OK because: // 1. this method (gridAPi.applyTransaction() is NOT accessible otherwise and making it public opens other cans of worms // 2. this UI will be reimplemented with Infinite Table anyway const addedRows = mapChangeHistoryRowData(addedChangeHistoryLogs, mainAdaptableInstance); const removedRows = mapChangeHistoryRowData(removedChangeHistoryLogs, mainAdaptableInstance); adaptableApi.agGridApi.applyTransactionAsync({ add: addedRows, remove: removedRows, }); }, [changeHistoryLog]); return React.createElement(React.Fragment, null); }; const buildAdaptableOptions = (mainAdaptableInstance, adaptableContainerId, agGridContainerId, undoChangeEnabled, onUndoChange, onClearRow) => { const mainAdaptableOptions = mainAdaptableInstance.adaptableOptions; const mainAdaptableTheme = mainAdaptableInstance.api.themeApi.getCurrentTheme(); const undoRowNode = (rowNode) => { const rowData = rowNode?.data; const changeKey = rowData?.['changeKey']; changeKey && onUndoChange(changeKey); }; const clearRow = (rowNode) => { const rowData = rowNode?.data; const changeKey = rowData?.['changeKey']; changeKey && onClearRow(changeKey); }; const actionColumnButton = buildActionColumnButton(mainAdaptableInstance.adaptableOptions?.dataChangeHistoryOptions, mainAdaptableInstance.api, undoRowNode, clearRow); let actionColumnOptions = undefined; if (actionColumnButton) { actionColumnOptions = { actionColumns: [ { columnId: 'undoActionColumn', friendlyName: ' ', rowScope: { ExcludeGroupRows: true, ExcludeSummaryRows: true, }, actionColumnSettings: { suppressMenu: true, suppressMovable: true }, actionColumnButton, }, ], }; } const options = { primaryKey: 'primaryKey', licenseKey: mainAdaptableOptions.licenseKey, userName: `${mainAdaptableOptions.userName}`, adaptableId: `${mainAdaptableOptions.adaptableId}::DataChangeHistory`, containerOptions: { adaptableContainer: adaptableContainerId, agGridContainer: agGridContainerId, }, entitlementOptions: { defaultAccessLevel: 'Hidden', }, actionColumnOptions: actionColumnOptions, initialState: { Layout: { Revision: Date.now(), CurrentLayout: 'DataChangeHistoryLayout', Layouts: [ { Name: 'DataChangeHistoryLayout', TableColumns: [ 'changeInfo.primaryKeyValue', 'changedColumnLabel', 'changeInfo.oldValue', 'changeInfo.newValue', 'changeInfo.changedAt', 'changeTriggerLabel', 'undoActionColumn', ], ColumnSorts: [ { ColumnId: 'changeInfo.changedAt', SortOrder: 'Desc', }, ], ColumnWidths: { undoActionColumn: 110, }, ColumnPinning: { undoActionColumn: 'right', }, AutoSizeColumns: true, }, ], }, FormatColumn: { Revision: Date.now(), FormatColumns: [ { Scope: { ColumnIds: ['changeInfo.changedAt'], }, DisplayFormat: { Formatter: 'DateFormatter', Options: { Pattern: `${mainAdaptableOptions.userInterfaceOptions?.dateInputOptions?.dateFormat} HH:mm:ss`, }, }, }, ], }, Theme: { Revision: Date.now(), CurrentTheme: mainAdaptableTheme, }, }, }; if (!undoChangeEnabled) { const layoutColumns = options.initialState.Layout.Layouts[0].TableColumns; if (layoutColumns.includes('undoActionColumn')) { // remove action column from layout options.initialState.Layout.Layouts[0].TableColumns = layoutColumns.filter((columnId) => columnId !== 'undoActionColumn'); } } return options; }; const buildGridOptions = (mainAdaptableInstance, changeHistoryLog) => { const mainPrimaryKeyColumnHeader = mainAdaptableInstance.api.columnApi.getPrimaryKeyColumn()?.friendlyName ?? 'Row ID'; const options = { loading: false, overlayNoRowsTemplate: '<div style="font-size: var(--ab-font-size-2);color: var(--ab-color-text-on-primary);">No Data Changes</div>', defaultColDef: { floatingFilter: true, filter: true, sortable: true, resizable: true, enableRowGroup: true, editable: false, enablePivot: false, enableValue: false, lockPinned: true, // default set to true to pass this to the action column suppressAutoSize: true, menuTabs: ['generalMenuTab', 'filterMenuTab'], }, autoGroupColumnDef: { sortable: true, }, columnDefs: [ { field: 'primaryKey', cellDataType: 'text', hide: true, }, { headerName: `Row (${mainPrimaryKeyColumnHeader})`, field: 'changeInfo.primaryKeyValue', cellDataType: 'text', suppressAutoSize: false, flex: 3, initialFlex: 3, }, { headerName: 'Column', field: 'changedColumnLabel', cellDataType: 'text', suppressAutoSize: false, flex: 3, initialFlex: 3, }, { headerName: 'Previous', field: 'changeInfo.oldValue', cellDataType: 'text', suppressAutoSize: false, flex: 3, initialFlex: 3, }, { headerName: 'New', field: 'changeInfo.newValue', cellDataType: 'text', suppressAutoSize: false, flex: 3, initialFlex: 3, }, { headerName: 'Changed', field: 'changeInfo.changedAt', cellDataType: 'date', suppressAutoSize: false, flex: 3, initialFlex: 3, }, { headerName: 'Source', field: 'changeTriggerLabel', cellDataType: 'text', suppressAutoSize: false, flex: 2, initialFlex: 2, }, ], rowData: mapChangeHistoryRowData(changeHistoryLog, mainAdaptableInstance), cellSelection: true, suppressColumnVirtualisation: false, sideBar: false, rowSelection: { mode: 'multiRow', }, skipHeaderOnAutoSize: true, }; return options; }; const mapChangeHistoryRowData = (changeHistoryLog = {}, mainAdaptableInstance) => { return Object.entries(changeHistoryLog).map(([changeKey, changeInfo]) => { return { primaryKey: `${changeKey}::${changeInfo.changedAt}`, changeKey, changeTriggerLabel: getChangeTriggerLabel(changeInfo), changedColumnLabel: getColumnHeaderLabel(changeInfo.column.columnId, mainAdaptableInstance), changeInfo, }; }); }; // memoize headers to avoid unnecessary main grid lookups const headerMap = new Map(); const getColumnHeaderLabel = (columnId, mainAdaptableInstance) => { if (!headerMap.has(columnId)) { headerMap.set(columnId, mainAdaptableInstance.api.columnApi.getColumnWithColumnId(columnId)?.friendlyName ?? columnId); } return headerMap.get(columnId); }; const getChangeTriggerLabel = (changeInfo) => { return changeInfo.trigger === 'tick' ? 'Ticking' : 'User Edit'; }; const filterChangeHistoryLog = (changeObject, filterValues) => { const result = {}; filterValues.forEach((allowedKey) => { result[allowedKey] = changeObject[allowedKey]; }); return result; };