UNPKG

@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
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); }, })))); };