blob: 85f406c08d189a2aeea77b27b8caf5558812a615 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { styled, t } from '@superset-ui/core';
import { Form, FormItem, FormProps } from 'src/components/Form';
import { Select } from 'src/components';
import { Col, InputNumber, Row } from 'src/common/components';
import Button from 'src/components/Button';
import {
COMPARATOR,
ConditionalFormattingConfig,
MULTIPLE_VALUE_COMPARATORS,
} from './types';
const FullWidthInputNumber = styled(InputNumber)`
width: 100%;
`;
const JustifyEnd = styled.div`
display: flex;
justify-content: flex-end;
`;
const colorSchemeOptions = [
{ value: 'rgb(0,255,0)', label: t('green') },
{ value: 'rgb(255,255,0)', label: t('yellow') },
{ value: 'rgb(255,0,0)', label: t('red') },
];
const operatorOptions = [
{ value: COMPARATOR.NONE, label: 'None' },
{ value: COMPARATOR.GREATER_THAN, label: '>' },
{ value: COMPARATOR.LESS_THAN, label: '<' },
{ value: COMPARATOR.GREATER_OR_EQUAL, label: '≥' },
{ value: COMPARATOR.LESS_OR_EQUAL, label: '≤' },
{ value: COMPARATOR.EQUAL, label: '=' },
{ value: COMPARATOR.NOT_EQUAL, label: '≠' },
{ value: COMPARATOR.BETWEEN, label: '< x <' },
{ value: COMPARATOR.BETWEEN_OR_EQUAL, label: '≤ x ≤' },
{ value: COMPARATOR.BETWEEN_OR_LEFT_EQUAL, label: '≤ x <' },
{ value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤' },
];
const targetValueValidator = (
compare: (targetValue: number, compareValue: number) => boolean,
rejectMessage: string,
) => (targetValue: number | string) => (
_: any,
compareValue: number | string,
) => {
if (
!targetValue ||
!compareValue ||
compare(Number(targetValue), Number(compareValue))
) {
return Promise.resolve();
}
return Promise.reject(new Error(rejectMessage));
};
const targetValueLeftValidator = targetValueValidator(
(target: number, val: number) => target > val,
t('This value should be smaller than the right target value'),
);
const targetValueRightValidator = targetValueValidator(
(target: number, val: number) => target < val,
t('This value should be greater than the left target value'),
);
const isOperatorMultiValue = (operator?: COMPARATOR) =>
operator && MULTIPLE_VALUE_COMPARATORS.includes(operator);
const isOperatorNone = (operator?: COMPARATOR) =>
!operator || operator === COMPARATOR.NONE;
const rulesRequired = [{ required: true, message: t('Required') }];
type GetFieldValue = Pick<Required<FormProps>['form'], 'getFieldValue'>;
const rulesTargetValueLeft = [
{ required: true, message: t('Required') },
({ getFieldValue }: GetFieldValue) => ({
validator: targetValueLeftValidator(getFieldValue('targetValueRight')),
}),
];
const rulesTargetValueRight = [
{ required: true, message: t('Required') },
({ getFieldValue }: GetFieldValue) => ({
validator: targetValueRightValidator(getFieldValue('targetValueLeft')),
}),
];
const targetValueLeftDeps = ['targetValueRight'];
const targetValueRightDeps = ['targetValueLeft'];
const shouldFormItemUpdate = (
prevValues: ConditionalFormattingConfig,
currentValues: ConditionalFormattingConfig,
) =>
isOperatorNone(prevValues.operator) !==
isOperatorNone(currentValues.operator) ||
isOperatorMultiValue(prevValues.operator) !==
isOperatorMultiValue(currentValues.operator);
const operatorField = (
<FormItem
name="operator"
label={t('Operator')}
rules={rulesRequired}
initialValue={operatorOptions[0].value}
>
<Select ariaLabel={t('Operator')} options={operatorOptions} />
</FormItem>
);
const renderOperatorFields = ({ getFieldValue }: GetFieldValue) =>
isOperatorNone(getFieldValue('operator')) ? (
<Row gutter={12}>
<Col span={6}>{operatorField}</Col>
</Row>
) : isOperatorMultiValue(getFieldValue('operator')) ? (
<Row gutter={12}>
<Col span={9}>
<FormItem
name="targetValueLeft"
label={t('Left value')}
rules={rulesTargetValueLeft}
dependencies={targetValueLeftDeps}
validateTrigger="onBlur"
trigger="onBlur"
>
<FullWidthInputNumber />
</FormItem>
</Col>
<Col span={6}>{operatorField}</Col>
<Col span={9}>
<FormItem
name="targetValueRight"
label={t('Right value')}
rules={rulesTargetValueRight}
dependencies={targetValueRightDeps}
validateTrigger="onBlur"
trigger="onBlur"
>
<FullWidthInputNumber />
</FormItem>
</Col>
</Row>
) : (
<Row gutter={12}>
<Col span={6}>{operatorField}</Col>
<Col span={18}>
<FormItem
name="targetValue"
label={t('Target value')}
rules={rulesRequired}
>
<FullWidthInputNumber />
</FormItem>
</Col>
</Row>
);
export const FormattingPopoverContent = ({
config,
onChange,
columns = [],
}: {
config?: ConditionalFormattingConfig;
onChange: (config: ConditionalFormattingConfig) => void;
columns: { label: string; value: string }[];
}) => (
<Form
onFinish={onChange}
initialValues={config}
requiredMark="optional"
layout="vertical"
>
<Row gutter={12}>
<Col span={12}>
<FormItem
name="column"
label={t('Column')}
rules={rulesRequired}
initialValue={columns[0]?.value}
>
<Select ariaLabel={t('Select column')} options={columns} />
</FormItem>
</Col>
<Col span={12}>
<FormItem
name="colorScheme"
label={t('Color scheme')}
rules={rulesRequired}
initialValue={colorSchemeOptions[0].value}
>
<Select ariaLabel={t('Color scheme')} options={colorSchemeOptions} />
</FormItem>
</Col>
</Row>
<FormItem noStyle shouldUpdate={shouldFormItemUpdate}>
{renderOperatorFields}
</FormItem>
<FormItem>
<JustifyEnd>
<Button htmlType="submit" buttonStyle="primary">
{t('Apply')}
</Button>
</JustifyEnd>
</FormItem>
</Form>
);