@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
388 lines (387 loc) • 20.4 kB
JavaScript
import { DataSource, InfiniteTable, } from '../../../../components/InfiniteTable';
import * as React from 'react';
import { Box, Flex, Text } from 'rebass';
import DropdownButton from '../../../../components/DropdownButton';
import FormLayout, { FormRow } from '../../../../components/FormLayout';
import Input from '../../../../components/Input';
import SimpleButton from '../../../../components/SimpleButton';
import { Tabs } from '../../../../components/Tabs';
import { Tag } from '../../../../components/Tag';
import { isValidOrderForColumnGroups, } from '../../../../AdaptableState/Common/AdaptableColumn';
import { useAdaptable } from '../../../AdaptableContext';
import { renderSelectionSection } from '../../../Components/ValueSelector';
import { useOnePageAdaptableWizardContext } from '../../../Wizard/OnePageAdaptableWizard';
import { columnFilter } from './Utilities';
import { Icon, NaturallySizedIcon } from '../../../../components/icons';
import { CheckBox } from '../../../../components/CheckBox';
import { clamp } from '../../../../Utilities/Helpers/Helper';
import { ColumnLabels } from '../Components/ColumnLabels';
import ArrayExtensions from '../../../../Utilities/Extensions/ArrayExtensions';
import { generateAutoRowGroupColumnForColumn, generateAutoRowGroupSingleColumn, generateAutoTreeSingleColumn, } from '../../../../Api/Implementation/ColumnApiImpl';
import { ReorderDraggable } from '../../../Components/ReorderDraggable';
import { AdaptableFormControlTextClear } from '../../../Components/Forms/AdaptableFormControlTextClear';
import { sortColumnIdsByOrder } from '../../../../layout-manager/src/sortColumnIdsByOrder';
import HelpBlock from '../../../../components/HelpBlock';
const PropertyOrderText = (props) => (React.createElement(Text, { fontWeight: 600, fontSize: 2 }, props.children));
const columnTypes = {
default: {
defaultFlex: 1,
},
};
export const ColumnsSectionSummary = () => {
const adaptable = useAdaptable();
const { data: layout } = useOnePageAdaptableWizardContext();
const rowHeight = 40;
const headerHeight = 40;
const ColumnIds = layout.TableColumns || layout.PivotColumns || [];
const tableHeight = headerHeight + clamp(rowHeight * ColumnIds.length, 100, 360);
const data = React.useMemo(() => {
return ColumnIds.map((columnId) => {
if (adaptable.api.columnApi.isPivotResultColumn(columnId) ||
adaptable.api.columnApi.isAutoRowGroupColumn(columnId)) {
return null;
}
const friendlyName = adaptable.api.columnApi.getFriendlyNameForColumnId(columnId, layout);
const header = layout.ColumnHeaders?.[columnId] ?? '';
const columnWidth = layout.ColumnWidths?.[columnId];
const columnPinning = layout.ColumnPinning?.[columnId];
let agg = (layout.TableAggregationColumns || []).find((agg) => agg.ColumnId === columnId)?.AggFunc;
agg = typeof agg === 'object' ? agg.type : agg;
const sortIndex = (layout?.ColumnSorts ?? []).findIndex((sort) => sort.ColumnId === columnId);
const sortOrder = sortIndex != -1
? {
index: sortIndex + 1,
sort: layout.ColumnSorts[sortIndex]?.SortOrder ?? null,
}
: {};
let grouping = layout.RowGroupedColumns?.indexOf?.(columnId);
grouping = grouping != -1 ? grouping + 1 : null;
const filter = layout.ColumnFilters?.find((colFilter) => colFilter.ColumnId === columnId);
return {
columnId,
agg,
friendlyName,
header,
columnWidth,
columnPinning,
sortOrder,
grouping,
filter,
};
}).filter(Boolean);
}, [layout]);
const columns = React.useMemo(() => {
return {
name: { header: 'Name', field: 'friendlyName', minWidth: 100 },
header: { header: 'Header', field: 'header', minWidth: 120 },
colId: { header: 'ID', field: 'columnId' },
agg: { header: 'Agg', field: 'agg', minWidth: 70 },
width: { header: 'Width', field: 'columnWidth', minWidth: 70 },
pin: { header: 'Pin', field: 'columnPinning', minWidth: 80 },
sort: {
header: 'Sort',
field: 'sortOrder',
minWidth: 50,
align: 'center',
render: (params) => {
if (!params.data?.sortOrder?.sort) {
return React.createElement(React.Fragment, null);
}
return (React.createElement(Flex, null,
React.createElement(Icon, { name: params.data?.sortOrder?.sort === 'Asc' ? 'arrow-up-long' : 'arrow-down-long' }),
' ',
React.createElement(PropertyOrderText, null, params.data?.sortOrder?.index ?? '')));
},
},
filter: {
header: 'Filter',
field: 'filter',
align: 'center',
minWidth: 50,
render: (params) => {
if (!params.data?.filter) {
return React.createElement(React.Fragment, null);
}
return React.createElement(CheckBox, { checked: true });
},
},
group: {
header: 'Group',
minWidth: 50,
field: 'columnGroupping',
align: 'center',
render: (params) => {
if (!params.data.grouping) {
return React.createElement(React.Fragment, null);
}
return (React.createElement(Flex, null,
React.createElement(CheckBox, { margin: 0, mr: 1, checked: true }),
React.createElement(PropertyOrderText, null, params.data?.grouping)));
},
},
};
}, []);
const tableDOMProps = {
style: {
height: '100%',
minWidth: '10rem',
minHeight: tableHeight,
},
};
return (React.createElement(DataSource, { data: data, primaryKey: "columnId" },
React.createElement(InfiniteTable, { columnTypes: columnTypes, rowHeight: rowHeight, columnHeaderHeight: headerHeight, domProps: tableDOMProps, columns: columns })));
};
const ColumnRow = (props) => {
const [isExpanded, setIsExpanded] = React.useState(false);
const adaptable = useAdaptable();
const initialHeader = adaptable.api.columnApi.getFriendlyNameForColumnId(props.column.columnId, props.layout);
const isRowGroupColumn = adaptable.api.columnApi.isAutoRowGroupColumn(props.column.columnId);
const customHeader = props.layout.ColumnHeaders?.[props.column.columnId] ?? initialHeader;
// column pinning
const columnPinning = props.layout.ColumnPinning?.[props.column.columnId] || 'None';
const pinningOptions = [
{ value: 'None', label: 'None', onClick: () => props.onPinChange(props.column.columnId, null) },
{
value: 'left',
label: 'Left',
onClick: () => props.onPinChange(props.column.columnId, 'left'),
},
{
value: 'right',
label: 'Right',
onClick: () => props.onPinChange(props.column.columnId, 'right'),
},
];
const { column } = props;
// width
const columnWidth = props.layout.ColumnWidths?.[column.columnId];
const visible = (props.layout.TableColumns.includes(column.columnId) &&
props.layout.ColumnVisibility?.[column.columnId] !== false) ||
isRowGroupColumn;
return (React.createElement(Box, { "data-name": props.column.columnId, className: "ab-Layout-Wizard__ColumnRow" },
React.createElement(Flex, { className: "ab-Layout-Wizard__ColumnRow__Header", mt: 1, mb: 1, onClick: () => setIsExpanded(!isExpanded) },
React.createElement(CheckBox, { "data-name": column.columnId, onChange: (checked) => {
props.onColumnVisibilityChange(column.columnId, checked);
}, disabled: column.hideable === false || isRowGroupColumn, onClick: (event) => {
event.stopPropagation();
}, checked: visible }),
React.createElement(Flex, { ml: 2, mr: 2, alignItems: "center", "data-name": "column-label" }, initialHeader),
props.column.columnGroup && props.column.columnGroup.groupCount > 1 ? (React.createElement(Box, { className: "ab-Layout-Wizard__ColumnRow__Title", ml: 2, mr: 2, padding: 1 },
"Column Group: ",
props.column.columnGroup.friendlyName)) : null,
React.createElement(Flex, { mr: 2 },
React.createElement(ColumnLabels, { showTitle: false, sortable: props.column.sortable, filterable: props.column.filterable, pivotable: props.column.pivotable, moveable: props.column.moveable, groupable: props.column.groupable, aggregatable: props.column.aggregatable })),
React.createElement(Box, { flex: 1 }),
React.createElement(SimpleButton, { "data-name": "expand-collapse", "data-value": isExpanded ? 'expanded' : 'collapsed', ml: 10, padding: 0, iconSize: 24, variant: "text", onClick: () => setIsExpanded(!isExpanded), icon: isExpanded ? 'triangle-up' : 'triangle-down' })),
isExpanded && (React.createElement(Box, { className: "ab-Layout-Wizard__ColumnRow__Expanded-Container", mb: 2, padding: 2, mt: 2 },
React.createElement(Flex, { mb: 2 },
React.createElement(FormLayout, { width: "100%", mr: 3 },
React.createElement(FormRow, { label: "ColumnId" },
React.createElement(Tag, null, props.column.columnId)),
React.createElement(FormRow, { label: "Header" },
React.createElement(Input, { "data-name": "column-header", className: "ab-Layout-Wizard__ColumnRow__Input", placeholder: "Custom name (optional)", onChange: () => {
props.onColumnNameChange(props.column.columnId, event.target.value);
}, value: customHeader })),
React.createElement(FormRow, { label: "Width" },
React.createElement(Input, { "data-name": "column-width", className: "ab-Layout-Wizard__ColumnRow__Input", type: "number", placeholder: "Column width", onChange: (event) => {
let value = parseFloat(event.target.value);
value = typeof value === 'number' && !isNaN(value) ? value : void 0;
props.onColumnWidthChange(props.column.columnId, value);
}, value: columnWidth })),
React.createElement(FormRow, { label: "Pinning" },
React.createElement(DropdownButton, { "data-name": "column-pinning", columns: ['label'], items: pinningOptions }, pinningOptions.find((option) => option.value === columnPinning).label))))))));
};
export const ColumnsSection = (props) => {
const adaptable = useAdaptable();
const { data: layout } = useOnePageAdaptableWizardContext();
const [searchInputValue, setSearchInputValue] = React.useState('');
// we don't want to rely on all the columns from the Grid itself
// since the "Row Groups" section of the editor can determine a change
// which is not reflected into AG Grid until we hit finish
//
// so we only rely on non-generated columns, which are the same
const allColumns = adaptable.api.columnApi.getUIAvailableColumns().filter((col) => {
return !col.isGeneratedRowGroupColumn;
});
// however, changes in RowGroupedColumns done in the previous step
// are reflected into the layout, so we use the latest layout.RowGroupedColumns
// to create new columns
if (layout.RowGroupedColumns) {
if (layout.RowGroupDisplayType === 'single') {
allColumns.unshift(generateAutoRowGroupSingleColumn());
}
else {
[...layout.RowGroupedColumns].reverse().forEach((col) => {
const groupCol = generateAutoRowGroupColumnForColumn(adaptable.api.columnApi.getColumnWithColumnId(col));
allColumns.unshift(groupCol);
});
}
}
if (adaptable.api.gridApi.isTreeDataGrid()) {
allColumns.unshift(generateAutoTreeSingleColumn());
}
const colIdToCol = allColumns.reduce((acc, col) => {
if (col) {
acc[col.columnId] = col;
}
return acc;
}, {});
const TableColumns = layout.TableColumns;
const ColumnOrderAllColumns = ArrayExtensions.sortArrayWithOrder(allColumns.map((col) => col.columnId), TableColumns, { sortUnorderedItems: false }).map((colId) => colIdToCol[colId]);
const currentOrder = searchInputValue
? ColumnOrderAllColumns.filter((col) => {
return columnFilter(col, searchInputValue);
})
: ColumnOrderAllColumns;
const handleColumnsChange = (newColumns) => {
if (searchInputValue) {
// there's a search in-place
// so the current `newColumns` does not actually contain all the columns
// so we need to recompute it
const newOrder = sortColumnIdsByOrder(
// given those are all the columns
ColumnOrderAllColumns.map((col) => col.columnId),
// we want to sort them to have this new order
// - this sorting will leave all ids not found in this new order untouched
newColumns.map((col) => col.columnId));
// so basically we now have the correct order, with all columns
newColumns = newOrder.map((colId) => colIdToCol[colId]);
}
const oldColumns = ColumnOrderAllColumns;
const columnIds = newColumns.map((col) => col.columnId);
if (!isValidOrderForColumnGroups({ oldColumns, newColumns })) {
return;
}
const ColumnVisibility = { ...layout.ColumnVisibility };
const columnOrderSet = new Set(TableColumns);
columnIds.forEach((colId) => {
if (!columnOrderSet.has(colId)) {
ColumnVisibility[colId] = false;
}
});
props.onChange({
...layout,
TableColumns: columnIds,
ColumnVisibility,
});
};
const handleColumnVisibilityChange = (columnId, visible) => {
const ColumnVisibility = { ...layout.ColumnVisibility };
let TableColumns = [...layout.TableColumns];
if (visible) {
delete ColumnVisibility[columnId];
}
else {
ColumnVisibility[columnId] = false;
}
const columnIds = ColumnOrderAllColumns.map((col) => col.columnId);
const idsToIndexes = new Map(columnIds.map((colId, index) => [colId, index]));
const columnOrderSet = new Set(TableColumns);
if (!columnOrderSet.has(columnId)) {
TableColumns = columnIds.filter((colId) => {
const isCurrent = columnOrderSet.has(colId);
if (isCurrent) {
return true;
}
// include all columns past the current column order
// that reach until the index of the currently checked column
const shouldInclude = idsToIndexes.get(colId) <= idsToIndexes.get(columnId);
if (shouldInclude) {
if (colId !== columnId && visible) {
// this is a column that's between the current column order limit
// and the currently checked column
// so we need to include it in order
// but make it invisible
ColumnVisibility[colId] = false;
}
}
return shouldInclude;
});
}
props.onChange({
...layout,
TableColumns: TableColumns,
ColumnVisibility,
});
};
const handlePinChange = (columnId, pinning) => {
props.onChange({
...layout,
ColumnPinning: {
...layout.ColumnPinning,
[columnId]: pinning,
},
});
};
const handleColumnNameChange = (columnId, headerName) => {
props.onChange({
...layout,
ColumnHeaders: {
...layout.ColumnHeaders,
[columnId]: headerName,
},
});
};
const handleColumnWidthChange = (columnId, width) => {
props.onChange({
...layout,
ColumnWidths: {
...layout.ColumnWidths,
[columnId]: width,
},
});
};
const visibleIds = layout.TableColumns.filter((colId) => {
return layout.ColumnVisibility?.[colId] !== false;
});
const toLabel = (colId) => adaptable.api.columnApi.getFriendlyNameForColumnId(colId, layout);
const toIdentifier = (colId) => colId;
return (React.createElement(Tabs, null,
React.createElement(Tabs.Tab, null, "Columns"),
React.createElement(Tabs.Content, { style: { overflow: 'hidden' } },
React.createElement(Flex, { mt: 2, mb: 3 },
React.createElement(ColumnLabels, { flexDirection: "row", showBoth: false, labels: {
Sortable: 'Sortable',
Filterable: 'Filterable',
Aggregatable: 'Aggregatable',
Groupable: 'Groupable',
Moveable: 'Moveable',
Pivotable: 'Pivotable',
}, sortable: true, filterable: true, moveable: true, pivotable: true, groupable: true, aggregatable: true })),
React.createElement(Flex, { mb: 1, alignItems: "center", flexDirection: "row" },
React.createElement(AdaptableFormControlTextClear, { value: searchInputValue, OnTextChange: setSearchInputValue, placeholder: "Type to search", style: { border: 0, margin: 3, width: '100%' } }),
React.createElement(Box, { flex: 1 })),
React.createElement(HelpBlock, { mb: 1, fontSize: 2, display: 'flex', "data-name": "column-help", alignItems: 'center' },
React.createElement(Box, null,
React.createElement(NaturallySizedIcon, { name: "info" })),
React.createElement(Text, { ml: 1 }, "Expand each Column to set Width, Pinnning and a custom Header")),
React.createElement(ReorderDraggable, { toIdentifier: (option) => `${option.columnId}`, order: currentOrder, renderOption: (option) => {
return (React.createElement(ColumnRow, { onColumnNameChange: handleColumnNameChange, onColumnWidthChange: handleColumnWidthChange, onColumnVisibilityChange: handleColumnVisibilityChange, onPinChange: handlePinChange, layout: layout, column: option }));
}, onChange: handleColumnsChange }),
renderSelectionSection({
options: visibleIds,
value: visibleIds,
toLabel,
toIdentifier,
isOptionDisabled: (colId) => adaptable.api.columnApi.isAutoRowGroupColumn(colId) ||
adaptable.api.columnApi.getColumnWithColumnId(colId)?.hideable === false,
xSelectedLabel: () => {
return `Selected Columns:`;
},
noSelectionLabel: `No Columns Selected`,
onChange: handleColumnsChange,
onSelectAll: () => {
props.onChange({
...layout,
ColumnVisibility: {},
TableColumns: ColumnOrderAllColumns.map((col) => col.columnId),
});
},
onClear: () => {
handleColumnsChange([]);
},
onClearOption: (colId) => {
handleColumnVisibilityChange(colId, false);
},
}))));
};