@adaptabletools/adaptable
Version:
Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
258 lines (257 loc) • 16.9 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import * as React from 'react';
import { CheckBox } from '../../../../components/CheckBox';
import FormLayout, { FormRow } from '../../../../components/FormLayout';
import SimpleButton from '../../../../components/SimpleButton';
import { ButtonNew } from '../../../Components/Buttons/ButtonNew';
import { Card } from '../../../../components/Card';
import { ColumnTag, Tag } from '../../../../components/Tag';
import { summarySupportedExpressions, WEIGHTED_AVERAGE_AGGREGATED_FUNCTION, } from '../../../../AdaptableState/Common/RowSummary';
import { mapColumnDataTypeToExpressionFunctionType } from '../../../../Utilities/adaptableQlUtils';
import { LayoutModuleId } from '../../../../Utilities/Constants/ModuleConstants';
import { aggregatedExpressionFunctions } from '../../../../Utilities/ExpressionFunctions/aggregatedScalarExpressionFunctions';
import { useAdaptable } from '../../../AdaptableContext';
import { SuspendToggleButton } from '../../../Components/Buttons/SuspendToggleButton';
import { ValueSelector } from '../../../Components/ValueSelector';
import { useOnePageAdaptableWizardContext } from '../../../Wizard/OnePageAdaptableWizard';
import { columnFilter } from './Utilities';
import { ColumnGroupTag } from '../../../Components/ColumnGroupTag';
import ArrayExtensions from '../../../../Utilities/Extensions/ArrayExtensions';
import { Box, Flex } from '../../../../components/Flex';
import { SingleSelect } from '../../../../components/NewSelect';
import { CollapsibleWizardCard, getWizardAccordionSectionClassName, useWizardCardAccordion, } from '../../../Wizard/CollapsibleWizardCard';
import { cn } from '../../../../lib/utils';
import { objectListActionButtonClassName } from '../../../Components/AdaptableObjectList/objectListActionButtonStyles';
export const areSummaryRowsValid = (layout) => {
if (!layout.RowSummaries)
return true;
let valid = true;
layout.RowSummaries?.find((rowSummary) => {
for (const [_, fn] of Object.entries(rowSummary.ColumnsMap ?? {})) {
if (!fn) {
valid = 'Each row summary column requires an aggregation function.';
return true;
}
}
});
return valid;
};
const rowSummaryCardId = (index) => `row-summary-${index}`;
const availableExpressionsForColumnTypeCache = new Map();
const getAvailableExpressionsForColumnType = (columnType, availableScalarExpressions) => {
const key = columnType;
if (availableExpressionsForColumnTypeCache.has(key)) {
return availableExpressionsForColumnTypeCache.get(key);
}
const columnInputType = mapColumnDataTypeToExpressionFunctionType(columnType);
const expressions = Object.keys(availableScalarExpressions)
.filter((availableExpression) => {
if (!aggregatedExpressionFunctions.includes(availableExpression)) {
return true;
}
return Boolean(summarySupportedExpressions.includes(availableExpression));
})
.map((expression) => {
const expressionDef = availableScalarExpressions[expression];
let firstArg = null;
if (!expressionDef?.inputs) {
return null;
}
if (Array.isArray(expressionDef?.inputs?.[0])) {
firstArg = expressionDef.inputs.find((input) => input.includes(columnInputType))?.[0];
}
else {
firstArg = expressionDef.inputs[0];
}
if (columnInputType === firstArg) {
return expression;
}
else {
return null;
}
})
.filter(Boolean);
availableExpressionsForColumnTypeCache.set(key, expressions);
return expressions;
};
const buildRowSummaryExpressionOptions = (column, availableScalarExpressions, layout) => {
const expressionOptions = getAvailableExpressionsForColumnType(column.dataType, availableScalarExpressions).map((expression) => ({
label: expression,
value: expression,
}));
const aggregation = layout.TableAggregationColumns?.find((agg) => agg.ColumnId === column.columnId)?.AggFunc;
if (aggregation && typeof aggregation === 'object' && aggregation.weightedColumnId) {
expressionOptions.push({
label: 'WEIGHTERD_AVG',
value: WEIGHTED_AVERAGE_AGGREGATED_FUNCTION,
});
}
return expressionOptions;
};
const getDefaultRowSummaryExpression = (column, availableScalarExpressions, layout) => {
const optionValues = buildRowSummaryExpressionOptions(column, availableScalarExpressions, layout).map((option) => option.value);
if (optionValues.includes('sum')) {
return 'sum';
}
const sumExpression = optionValues.find((value) => value.toLowerCase() === 'sum');
if (sumExpression) {
return sumExpression;
}
return optionValues[0] ?? null;
};
const RowSummaryPositionTag = ({ position }) => _jsx(Tag, { className: "twa:shrink-0", children: position });
const RowSummaryCardSummary = ({ rowSummary }) => {
const adaptable = useAdaptable();
const columnIds = Object.keys(rowSummary.ColumnsMap ?? {}).filter((colId) => colId !== 'Source' && colId !== 'Uuid');
if (!columnIds.length) {
return _jsx(Tag, { children: "No columns" });
}
return (_jsx(Flex, { flexWrap: "wrap", className: "twa:gap-1", children: columnIds.map((colId) => (_jsxs(ColumnTag, { children: [rowSummary.ColumnsMap[colId], "(", adaptable.api.columnApi.getFriendlyNameForColumnId(colId), ")"] }, colId))) }));
};
export const RowSummarySectionSummary = () => {
const adaptable = useAdaptable();
const { data: layout } = useOnePageAdaptableWizardContext();
return (_jsx(Box, { className: "twa:flex twa:flex-col", children: layout.RowSummaries?.length ? (layout.RowSummaries.map((rowSummary, index) => {
return (_jsxs(Box, { className: "twa:mr-1 twa:mb-1", children: [rowSummary.Position, ":", ' ', Object.keys(rowSummary.ColumnsMap).map((colId) => {
if (colId === 'Source' || colId === 'Uuid')
return null;
return (_jsxs(ColumnTag, { className: "twa:mr-1", children: [rowSummary.ColumnsMap[colId], "(", adaptable.api.columnApi.getFriendlyNameForColumnId(colId), ")"] }, colId));
})] }, index));
})) : (_jsx(Tag, { children: "No Row Summaries" })) }));
};
const RowSummaryEditorForm = React.memo(({ rowSummary, onChange, availableScalarExpressions, }) => {
const { data: layout } = useOnePageAdaptableWizardContext();
const adaptable = useAdaptable();
const columns = React.useMemo(() => {
const colIds = adaptable.api.columnApi
.getUIAvailableColumns()
.filter((column) => {
if (!['number', 'text', 'date'].includes(column.dataType))
return false;
return layout.TableColumns?.includes?.(column.columnId);
})
.map((c) => c.columnId);
return ArrayExtensions.sortArrayWithOrder(colIds, Object.keys(rowSummary.ColumnsMap), {
sortUnorderedItems: false,
}).map((colId) => adaptable.api.columnApi.getColumnWithColumnId(colId));
}, [rowSummary.ColumnsMap]);
return (_jsxs(Box, { className: "twa:flex twa:flex-col twa:gap-3 twa:h-full twa:overflow-hidden", children: [_jsxs(FormLayout, { children: [_jsx(FormRow, { label: "Position", children: _jsx(SingleSelect, { items: [
{
label: 'Top',
value: 'Top',
},
{
label: 'Bottom',
value: 'Bottom',
},
], value: rowSummary.Position, onValueChange: (position) => {
onChange({
...rowSummary,
Position: position,
});
} }) }), _jsx(FormRow, { label: "", children: _jsx(CheckBox, { checked: rowSummary.IncludeOnlyFilteredRows ?? true, onChange: (IncludeOnlyFilteredRows) => {
onChange({
...rowSummary,
IncludeOnlyFilteredRows,
});
}, children: "Include Only Filtered Rows" }) })] }), _jsxs(Card, { shadow: false, className: "twa:flex-1 twa:min-h-0 twa:overflow-hidden twa:flex twa:flex-col", children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Column Aggregations" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Select columns and choose an aggregation function for each" })] }), _jsx(Card.Body, { className: "twa:flex-1 twa:min-h-0 twa:overflow-hidden twa:p-1", children: _jsx(ValueSelector, { showFilterInput: true, className: "twa:max-h-full twa:min-h-0", toggleSelectionOnRowClick: false, filter: columnFilter, toIdentifier: (column) => column.columnId, toLabel: (option) => option.friendlyName ?? option.columnId, options: columns, optionLayout: "label-beside-checkbox", toListLabel: (column) => {
const label = column.friendlyName ?? column.columnId;
const disabled = !(column.columnId in (rowSummary.ColumnsMap ?? {}));
if (disabled) {
return (_jsxs(Flex, { alignItems: "center", className: "twa:gap-2", children: [label, _jsx(ColumnGroupTag, { column: column }), _jsx(SingleSelect, { disabled: true, items: [{ label: 'Select Aggregation', value: null }] })] }));
}
const expressionOptions = buildRowSummaryExpressionOptions(column, availableScalarExpressions, layout);
const expression = rowSummary.ColumnsMap[column.columnId];
return (_jsxs(Flex, { children: [_jsxs(Flex, { className: "twa:mr-2", alignItems: 'center', children: [label, _jsx(ColumnGroupTag, { column: column })] }), _jsx(SingleSelect, { value: expression, items: expressionOptions, onValueChange: (expression) => {
onChange({
...rowSummary,
ColumnsMap: {
...rowSummary.ColumnsMap,
[column.columnId]: expression,
},
});
} })] }));
}, value: Object.keys(rowSummary.ColumnsMap), onChange: (colIds) => {
const newColumnsMap = {};
colIds.forEach((colId) => {
const existing = rowSummary.ColumnsMap[colId];
if (existing) {
newColumnsMap[colId] = existing;
return;
}
const column = adaptable.api.columnApi.getColumnWithColumnId(colId);
newColumnsMap[colId] = column
? getDefaultRowSummaryExpression(column, availableScalarExpressions, layout)
: null;
});
onChange({
...rowSummary,
ColumnsMap: newColumnsMap,
});
} }) })] })] }));
});
export const RowSummarySection = (props) => {
const adaptable = useAdaptable();
const { data: layout } = useOnePageAdaptableWizardContext();
const rowSummaries = layout.RowSummaries ?? [];
const availableScalarExpressions = React.useMemo(() => {
const sytemExpressions = adaptable.api.internalApi
.getQueryLanguageService()
.getModuleExpressionFunctionsMap(LayoutModuleId).aggregatedScalarFunctions;
return sytemExpressions;
}, []);
const { bindCard, hasExpandedCard, expandedFillsSpace, expandedId, setExpandedId } = useWizardCardAccordion(null);
const handleAddRowSummary = () => {
const newIndex = rowSummaries.length;
props.onChange({
...layout,
RowSummaries: [
...rowSummaries,
{
Position: 'Top',
ColumnsMap: {},
IncludeOnlyFilteredRows: true,
},
],
});
setExpandedId(rowSummaryCardId(newIndex));
};
const handleDeleteRowSummary = (index) => {
const cardId = rowSummaryCardId(index);
const newSummaries = [...rowSummaries];
newSummaries.splice(index, 1);
props.onChange({
...layout,
RowSummaries: newSummaries,
});
if (expandedId === cardId) {
setExpandedId(null);
return;
}
if (expandedId?.startsWith('row-summary-')) {
const expandedIndex = Number(expandedId.replace('row-summary-', ''));
if (!Number.isNaN(expandedIndex) && expandedIndex > index) {
setExpandedId(rowSummaryCardId(expandedIndex - 1));
}
}
};
return (_jsxs(Box, { className: cn(getWizardAccordionSectionClassName(hasExpandedCard, expandedFillsSpace), 'twa:p-3'), children: [_jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:mb-2 twa:max-w-[520px] twa:shrink-0", children: "Add summary rows at the top or bottom of the grid with aggregated column values" }), _jsx(Flex, { className: "twa:justify-end twa:mb-2 twa:shrink-0", children: _jsx(ButtonNew, { onClick: handleAddRowSummary, children: "Add Row Summary" }) }), _jsx(Flex, { flexDirection: "column", className: "twa:gap-3 twa:min-h-0 twa:flex-1 twa:overflow-y-auto", children: rowSummaries.map((rowSummary, index) => {
const cardBinding = bindCard(rowSummaryCardId(index), { fillAvailable: true });
return (_jsx(CollapsibleWizardCard, { ...cardBinding, surface: "panel", "data-name": `row-summary-${index}`, title: `Row Summary ${index + 1}`, help: "Configure position, filters, and column aggregations for this summary row", collapsedHelp: false, compactSummary: _jsx(RowSummaryPositionTag, { position: rowSummary.Position }), headerVisual: !cardBinding.expanded ? (_jsx(RowSummaryPositionTag, { position: rowSummary.Position })) : undefined, headerActions: _jsxs(_Fragment, { children: [cardBinding.expanded ? (_jsx(SuspendToggleButton, { className: objectListActionButtonClassName('suspend'), onSuspend: () => {
const newSummaries = [...rowSummaries];
newSummaries[index] = { ...rowSummary, IsSuspended: true };
props.onChange({ ...layout, RowSummaries: newSummaries });
}, onUnSuspend: () => {
const newSummaries = [...rowSummaries];
newSummaries[index] = { ...rowSummary, IsSuspended: false };
props.onChange({ ...layout, RowSummaries: newSummaries });
}, suspendableObject: rowSummary })) : null, _jsx(SimpleButton, { icon: "delete", variant: "text", tooltip: "Delete row summary", className: objectListActionButtonClassName('delete'), onClick: () => handleDeleteRowSummary(index) })] }), summary: _jsx(RowSummaryCardSummary, { rowSummary: rowSummary }), className: cn('twa:overflow-hidden twa:flex twa:flex-col', cardBinding.expanded && 'twa:min-h-[500px]'), bodyClassName: " twa:overflow-hidden twa:flex twa:flex-col twa:pt-0!", children: _jsx(RowSummaryEditorForm, { rowSummary: rowSummary, availableScalarExpressions: availableScalarExpressions, onChange: (nextRowSummary) => {
const newSummaries = [...rowSummaries];
newSummaries[index] = nextRowSummary;
props.onChange({
...layout,
RowSummaries: newSummaries,
});
} }) }, rowSummaryCardId(index)));
}) })] }));
};