more validations

This commit is contained in:
Chika 2020-09-09 21:40:44 +03:00
parent 3808ae3a3e
commit a1102d5f9c
14 changed files with 204 additions and 156 deletions

View File

@ -38,7 +38,7 @@ const renderElements = ({ elements }) => {
return elements.map((element, ie) => {
const { title: elementTitle, Component, props: elementProps } = element;
return (
<Flex flexDirection="column" key={ie} my="5px">
<Flex flexDirection="column" key={ie}>
<ElementTitle>{elementTitle}</ElementTitle>
<Component {...elementProps} />
</Flex>
@ -64,7 +64,7 @@ const renderBlocks = ({ blocks }) => {
'2 0 45%',
'2 0 45%',
'2 0 45%',
'3 0 30%'
'3 0 30%',
]}
mx="10px"
my="15px"

View File

@ -7,7 +7,12 @@ import Radio from 'client/Elements/Radio';
import Select from 'client/Elements/Select';
import Switch from 'client/Elements/Switch';
import TextArea from 'client/Elements/TextArea';
import { validateInn } from 'client/tools/validate';
import {
validateInn,
validateKpp,
validatePhone,
validateEmail,
} from 'client/tools/validate';
export default [
{
@ -113,7 +118,10 @@ export default [
title: 'КПП',
Component: Input,
props: {
// TODO regular min: 9, max: 9
validation: {
errorMessage: 'Некорректный КПП',
validator: validateKpp,
},
name: 'tbxKPP',
valueName: 'KPP',
},
@ -156,7 +164,10 @@ export default [
type: 'tel',
name: 'tbxPhoneNumber',
valueName: 'phoneNumber',
pattern: undefined,
validation: {
errorMessage: 'Некорректный номер телефона',
validator: validatePhone,
},
//TODO: mask + 7(999) 999 99 99
},
},
@ -167,8 +178,11 @@ export default [
type: 'email',
name: 'tbxEmailAddress',
valueName: 'emailAddress',
pattern: undefined,
//TODO check email valid
//TODO email mask
validation: {
errorMessage: 'Некорректный E-mail',
validator: validateEmail,
},
},
},
],

View File

@ -1,29 +1,28 @@
import { Checkbox as AntCheckbox } from 'antd';
import { Checkbox as AntCheckbox, Form } from 'antd';
import { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue';
import { Box } from 'client/UIKit/grid';
import { DEFAULT_DEBOUNCE_DELAY } from 'core/constants/debounce';
import { Status } from 'core/types/statuses';
import { observer } from 'mobx-react';
import React from 'react';
import { DEFAULT_DEBOUNCE_DELAY } from 'core/constants/debounce';
const Checkbox = ({ name, readonly, valueName, computedValue }) => {
const { value, setCurrentValue } = useStoreValue({
computedValue,
valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY
debounceDelay: DEFAULT_DEBOUNCE_DELAY,
});
const { status } = useStatus(name);
return (
<Box>
<Form.Item>
<AntCheckbox
disabled={status === Status.Disabled}
readonly={readonly}
checked={value}
onChange={e => setCurrentValue(e.target.checked)}
/>
</Box>
</Form.Item>
);
};

View File

@ -1,4 +1,4 @@
import { InputNumber as AntInputNumber } from 'antd';
import { InputNumber as AntInputNumber, Form } from 'antd';
import { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue';
import { Status } from 'core/types/statuses';
@ -16,31 +16,33 @@ const InputNumber = ({
parser,
placeholder,
valueName,
computedValue
computedValue,
}) => {
const { value, setCurrentValue } = useStoreValue({
computedValue,
valueName,
debounceDelay: TEXT_INPUT_DEBOUNCE_DELAY
debounceDelay: TEXT_INPUT_DEBOUNCE_DELAY,
});
const { status } = useStatus(name);
return (
<AntInputNumber
disabled={status === Status.Disabled}
readOnly={readonly}
placeholder={placeholder}
style={{
width: '100%'
}}
min={min}
max={max}
step={step}
formatter={formatter}
parser={parser}
onChange={value => setCurrentValue(value)}
value={value}
/>
<Form.Item>
<AntInputNumber
disabled={status === Status.Disabled}
readOnly={readonly}
placeholder={placeholder}
style={{
width: '100%',
}}
min={min}
max={max}
step={step}
formatter={formatter}
parser={parser}
onChange={value => setCurrentValue(value)}
value={value}
/>
</Form.Item>
);
};

View File

@ -1,4 +1,4 @@
import { Radio as AntRadio } from 'antd';
import { Radio as AntRadio, Form } from 'antd';
import { useOptions } from 'client/hooks/useOptions';
import { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue';
@ -11,54 +11,44 @@ const Radio = ({ name, style, computedValue, valueName }) => {
const { value, setCurrentValue } = useStoreValue({
computedValue,
valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY
debounceDelay: DEFAULT_DEBOUNCE_DELAY,
});
const { status } = useStatus(name);
const { options } = useOptions(name);
return (
<AntRadio.Group
disabled={status === Status.Disabled}
buttonStyle={style === 'button' && 'solid'}
value={value}
onChange={e => {
if (e && e.target) setCurrentValue(e.target.value);
}}
>
{options.map((option, i) => {
switch (style) {
case 'button':
if (option) {
return (
<AntRadio.Button key={i} value={option.value || ''}>
{option.name}
</AntRadio.Button>
);
}
break;
default:
if (option)
return (
<AntRadio
key={i}
value={option.value || ''}
style={styles.radio}
>
{option.name}
</AntRadio>
);
break;
}
})}
</AntRadio.Group>
<Form.Item>
<AntRadio.Group
disabled={status === Status.Disabled}
buttonStyle={style === 'button' && 'solid'}
value={value}
onChange={e => {
if (e && e.target) setCurrentValue(e.target.value);
}}
>
{options.map((option, i) => {
if (style === 'button') {
return (
<AntRadio.Button key={i} value={option.value || ''}>
{option.name}
</AntRadio.Button>
);
}
return (
<AntRadio key={i} value={option.value || ''} style={styles.radio}>
{option.name}
</AntRadio>
);
})}
</AntRadio.Group>
</Form.Item>
);
};
const styles = {
radio: {
display: 'block'
}
display: 'block',
},
};
export default observer(Radio);

View File

@ -1,4 +1,4 @@
import { Select as AntSelect } from 'antd';
import { Select as AntSelect, Form } from 'antd';
import { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue';
import { Status } from 'core/types/statuses';
@ -11,29 +11,32 @@ const Select = ({ name, showSearch, computedValue, valueName }) => {
const { value, setCurrentValue } = useStoreValue({
computedValue,
valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY
debounceDelay: DEFAULT_DEBOUNCE_DELAY,
});
const { status } = useStatus(name);
const { options, filter } = useOptions(name);
return (
<AntSelect
disabled={status === Status.Disabled}
showSearch={showSearch}
optionFilterProp="children"
filterOption={filter}
value={value}
onChange={val => setCurrentValue(val)}
>
{options.map((option, i) => {
if (option)
return (
<AntSelect.Option key={i} value={option.value}>
{option.name}
</AntSelect.Option>
);
})}
</AntSelect>
<Form.Item>
<AntSelect
disabled={status === Status.Disabled}
showSearch={showSearch}
optionFilterProp="children"
filterOption={filter}
value={value}
onChange={val => setCurrentValue(val)}
>
{options.map((option, i) => {
if (option)
return (
<AntSelect.Option key={i} value={option.value}>
{option.name}
</AntSelect.Option>
);
return null;
})}
</AntSelect>
</Form.Item>
);
};

View File

@ -1,22 +1,21 @@
import { Switch as AntSwitch } from 'antd';
import { Form, Switch as AntSwitch } from 'antd';
import { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue';
import { Box } from 'client/UIKit/grid';
import { DEFAULT_DEBOUNCE_DELAY } from 'core/constants/debounce';
import { Status } from 'core/types/statuses';
import { observer } from 'mobx-react';
import React from 'react';
import { DEFAULT_DEBOUNCE_DELAY } from 'core/constants/debounce';
const Switch = ({ name, valueName, computedValue }) => {
const { value, setCurrentValue } = useStoreValue({
computedValue,
valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY
debounceDelay: DEFAULT_DEBOUNCE_DELAY,
});
const { status } = useStatus(name);
return (
<Box>
<Form.Item>
<AntSwitch
disabled={status === Status.Disabled}
checked={value}
@ -24,7 +23,7 @@ const Switch = ({ name, valueName, computedValue }) => {
setCurrentValue(checked);
}}
/>
</Box>
</Form.Item>
);
};

View File

@ -11,20 +11,18 @@ const TextArea = ({
readonly,
placeholder,
valueName,
computedValue
computedValue,
}) => {
const { value, setCurrentValue } = useStoreValue({
computedValue,
valueName,
debounceDelay: TEXT_INPUT_DEBOUNCE_DELAY
debounceDelay: TEXT_INPUT_DEBOUNCE_DELAY,
});
const { status } = useStatus(name);
return (
<AntInput.TextArea
style={{
height: '150px'
}}
autoSize={{ minRows: 5, maxRows: 8 }}
disabled={status === Status.Disabled}
readOnly={readonly}
placeholder={placeholder}

View File

@ -16,9 +16,10 @@ import { ICalculationStore } from 'core/types/stores';
const CalculationStore: ICalculationStore = observable(
assignProperties(
{
statuses: initialStatuses,
validations: {},
values: initialValues,
options: initialOptions,
statuses: initialStatuses,
filters: {},
getValue(sourceValueName: ValuesNames) {
@ -33,14 +34,14 @@ const CalculationStore: ICalculationStore = observable(
},
setStatus(elementName: ElementsNames, status: Status) {
this.statuses[elementName] = status;
}
},
},
computedEffects
)
computedEffects,
),
);
autorunEffects.map(autorunEffect =>
autorun(autorunEffect(CalculationStore, CommonStore))
autorun(autorunEffect(CalculationStore, CommonStore)),
);
reactionEffects.map(reactionEffectBuilder => {

View File

@ -1,49 +1,90 @@
export function validateInn(inn) {
if (typeof inn === 'number') {
inn = inn.toString();
} else if (typeof inn !== 'string') {
inn = '';
import validator from 'validator';
const isINNIndividual = value => {
const valueToString = value ? value.toString() : '';
const getN = index => parseInt(valueToString[index]);
if (valueToString.length === 12) {
const dgt11 =
((7 * getN(0) +
2 * getN(1) +
4 * getN(2) +
10 * getN(3) +
3 * getN(4) +
5 * getN(5) +
9 * getN(6) +
4 * getN(7) +
6 * getN(8) +
8 * getN(9)) %
11) %
10;
const dgt12 =
((3 * getN(0) +
7 * getN(1) +
2 * getN(2) +
4 * getN(3) +
10 * getN(4) +
3 * getN(5) +
5 * getN(6) +
9 * getN(7) +
4 * getN(8) +
6 * getN(9) +
8 * getN(10)) %
11) %
10;
return getN(10) === dgt11 && getN(11) === dgt12;
}
var checkDigit = function (inn, coefficients) {
var n = 0;
for (var i in coefficients) {
n += coefficients[i] * inn[i];
}
return parseInt((n % 11) % 10);
};
switch (inn.length) {
case 10:
var n10 = checkDigit(inn, [2, 4, 10, 3, 5, 9, 4, 6, 8]);
return n10 === parseInt(inn[9]);
case 12:
var n11 = checkDigit(inn, [7, 2, 4, 10, 3, 5, 9, 4, 6, 8]);
var n12 = checkDigit(inn, [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]);
return n11 === parseInt(inn[10]) && n12 === parseInt(inn[11]);
default: {
return false;
}
return false;
};
const isINNLegalEntity = value => {
const valueToString = value ? value.toString() : '';
const getN = index => parseInt(valueToString[index]);
if (valueToString.length === 10) {
const dgt10 =
((2 * getN(0) +
4 * getN(1) +
10 * getN(2) +
3 * getN(3) +
5 * getN(4) +
9 * getN(5) +
4 * getN(6) +
6 * getN(7) +
8 * getN(8)) %
11) %
10;
return getN(9) === dgt10;
}
return false;
};
export function validateInn(value) {
const stringValue = value ? value.toString() : '';
if (stringValue.length === 10) {
return isINNLegalEntity(stringValue);
}
if (stringValue.length === 12) {
return isINNIndividual(stringValue);
}
return false;
}
export function validateKpp(kpp, error) {
var result = false;
if (typeof kpp === 'number') {
kpp = kpp.toString();
} else if (typeof kpp !== 'string') {
kpp = '';
}
if (!kpp.length) {
error.code = 1;
error.message = 'КПП пуст';
} else if (kpp.length !== 9) {
error.code = 2;
error.message =
'КПП может состоять только из 9 знаков (цифр или заглавных букв латинского алфавита от A до Z)';
} else if (!/^[0-9]{4}[0-9A-Z]{2}[0-9]{3}$/.test(kpp)) {
error.code = 3;
error.message = 'Неправильный формат КПП';
} else {
result = true;
}
return result;
export function validateKpp(value) {
const stringValue = value ? value.toString() : '';
if (stringValue.length !== 9) return false;
if (!stringValue.match(/\d{4}[\dA-Z][\dA-Z]\d{3}/)) return false;
return true;
}
export function validatePhone(value) {
const stringValue = value ? value.toString() : '';
return validator.isMobilePhone(stringValue, ['ru-RU']);
}
export function validateEmail(value) {
const stringValue = value ? value.toString() : '';
return validator.isEmail(stringValue);
}

View File

@ -1,6 +1,6 @@
import { TElements } from 'core/types/elements';
const initialOptions: TElements = {
const initialOptions: TElements<any> = {
selectChannel: [
{
name: 'От агента-ФЛ-сотрудника поставщика',

View File

@ -1,2 +1,2 @@
export const TEXT_INPUT_DEBOUNCE_DELAY = 800;
export const TEXT_INPUT_DEBOUNCE_DELAY = 650;
export const DEFAULT_DEBOUNCE_DELAY = 0;

View File

@ -133,6 +133,6 @@ export type ElementsNames =
| 'tbxOpportunityNumber'
| 'radioInsuranceOPF';
export type TElements = {
[elementName in ElementsNames]?: any;
export type TElements<T> = {
[elementName in ElementsNames]?: T;
};

View File

@ -3,10 +3,11 @@ import { TElements, ElementsNames } from './elements';
import { TStatuses, Status } from './statuses';
export interface ICalculationStore {
values: TValues;
options: TElements;
statuses: TStatuses;
filters: any;
validations: TElements<boolean>;
values: TValues;
options: TElements<any>;
filters: TElements<any>;
getValue: (sourceValueName: ValuesNames) => any;
getStatus: (elementName: ElementsNames) => Status;