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

View File

@ -7,7 +7,12 @@ import Radio from 'client/Elements/Radio';
import Select from 'client/Elements/Select'; import Select from 'client/Elements/Select';
import Switch from 'client/Elements/Switch'; import Switch from 'client/Elements/Switch';
import TextArea from 'client/Elements/TextArea'; import TextArea from 'client/Elements/TextArea';
import { validateInn } from 'client/tools/validate'; import {
validateInn,
validateKpp,
validatePhone,
validateEmail,
} from 'client/tools/validate';
export default [ export default [
{ {
@ -113,7 +118,10 @@ export default [
title: 'КПП', title: 'КПП',
Component: Input, Component: Input,
props: { props: {
// TODO regular min: 9, max: 9 validation: {
errorMessage: 'Некорректный КПП',
validator: validateKpp,
},
name: 'tbxKPP', name: 'tbxKPP',
valueName: 'KPP', valueName: 'KPP',
}, },
@ -156,7 +164,10 @@ export default [
type: 'tel', type: 'tel',
name: 'tbxPhoneNumber', name: 'tbxPhoneNumber',
valueName: 'phoneNumber', valueName: 'phoneNumber',
pattern: undefined, validation: {
errorMessage: 'Некорректный номер телефона',
validator: validatePhone,
},
//TODO: mask + 7(999) 999 99 99 //TODO: mask + 7(999) 999 99 99
}, },
}, },
@ -167,8 +178,11 @@ export default [
type: 'email', type: 'email',
name: 'tbxEmailAddress', name: 'tbxEmailAddress',
valueName: 'emailAddress', valueName: 'emailAddress',
pattern: undefined, //TODO email mask
//TODO check email valid 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 { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue'; 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 { Status } from 'core/types/statuses';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React from 'react'; import React from 'react';
import { DEFAULT_DEBOUNCE_DELAY } from 'core/constants/debounce';
const Checkbox = ({ name, readonly, valueName, computedValue }) => { const Checkbox = ({ name, readonly, valueName, computedValue }) => {
const { value, setCurrentValue } = useStoreValue({ const { value, setCurrentValue } = useStoreValue({
computedValue, computedValue,
valueName, valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY debounceDelay: DEFAULT_DEBOUNCE_DELAY,
}); });
const { status } = useStatus(name); const { status } = useStatus(name);
return ( return (
<Box> <Form.Item>
<AntCheckbox <AntCheckbox
disabled={status === Status.Disabled} disabled={status === Status.Disabled}
readonly={readonly} readonly={readonly}
checked={value} checked={value}
onChange={e => setCurrentValue(e.target.checked)} 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 { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue'; import { useStoreValue } from 'client/hooks/useStoreValue';
import { Status } from 'core/types/statuses'; import { Status } from 'core/types/statuses';
@ -16,31 +16,33 @@ const InputNumber = ({
parser, parser,
placeholder, placeholder,
valueName, valueName,
computedValue computedValue,
}) => { }) => {
const { value, setCurrentValue } = useStoreValue({ const { value, setCurrentValue } = useStoreValue({
computedValue, computedValue,
valueName, valueName,
debounceDelay: TEXT_INPUT_DEBOUNCE_DELAY debounceDelay: TEXT_INPUT_DEBOUNCE_DELAY,
}); });
const { status } = useStatus(name); const { status } = useStatus(name);
return ( return (
<AntInputNumber <Form.Item>
disabled={status === Status.Disabled} <AntInputNumber
readOnly={readonly} disabled={status === Status.Disabled}
placeholder={placeholder} readOnly={readonly}
style={{ placeholder={placeholder}
width: '100%' style={{
}} width: '100%',
min={min} }}
max={max} min={min}
step={step} max={max}
formatter={formatter} step={step}
parser={parser} formatter={formatter}
onChange={value => setCurrentValue(value)} parser={parser}
value={value} 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 { useOptions } from 'client/hooks/useOptions';
import { useStatus } from 'client/hooks/useStatus'; import { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue'; import { useStoreValue } from 'client/hooks/useStoreValue';
@ -11,54 +11,44 @@ const Radio = ({ name, style, computedValue, valueName }) => {
const { value, setCurrentValue } = useStoreValue({ const { value, setCurrentValue } = useStoreValue({
computedValue, computedValue,
valueName, valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY debounceDelay: DEFAULT_DEBOUNCE_DELAY,
}); });
const { status } = useStatus(name); const { status } = useStatus(name);
const { options } = useOptions(name); const { options } = useOptions(name);
return ( return (
<AntRadio.Group <Form.Item>
disabled={status === Status.Disabled} <AntRadio.Group
buttonStyle={style === 'button' && 'solid'} disabled={status === Status.Disabled}
value={value} buttonStyle={style === 'button' && 'solid'}
onChange={e => { value={value}
if (e && e.target) setCurrentValue(e.target.value); onChange={e => {
}} if (e && e.target) setCurrentValue(e.target.value);
> }}
{options.map((option, i) => { >
switch (style) { {options.map((option, i) => {
case 'button': if (style === 'button') {
if (option) { return (
return ( <AntRadio.Button key={i} value={option.value || ''}>
<AntRadio.Button key={i} value={option.value || ''}> {option.name}
{option.name} </AntRadio.Button>
</AntRadio.Button> );
); }
} return (
break; <AntRadio key={i} value={option.value || ''} style={styles.radio}>
{option.name}
default: </AntRadio>
if (option) );
return ( })}
<AntRadio </AntRadio.Group>
key={i} </Form.Item>
value={option.value || ''}
style={styles.radio}
>
{option.name}
</AntRadio>
);
break;
}
})}
</AntRadio.Group>
); );
}; };
const styles = { const styles = {
radio: { radio: {
display: 'block' display: 'block',
} },
}; };
export default observer(Radio); 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 { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue'; import { useStoreValue } from 'client/hooks/useStoreValue';
import { Status } from 'core/types/statuses'; import { Status } from 'core/types/statuses';
@ -11,29 +11,32 @@ const Select = ({ name, showSearch, computedValue, valueName }) => {
const { value, setCurrentValue } = useStoreValue({ const { value, setCurrentValue } = useStoreValue({
computedValue, computedValue,
valueName, valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY debounceDelay: DEFAULT_DEBOUNCE_DELAY,
}); });
const { status } = useStatus(name); const { status } = useStatus(name);
const { options, filter } = useOptions(name); const { options, filter } = useOptions(name);
return ( return (
<AntSelect <Form.Item>
disabled={status === Status.Disabled} <AntSelect
showSearch={showSearch} disabled={status === Status.Disabled}
optionFilterProp="children" showSearch={showSearch}
filterOption={filter} optionFilterProp="children"
value={value} filterOption={filter}
onChange={val => setCurrentValue(val)} value={value}
> onChange={val => setCurrentValue(val)}
{options.map((option, i) => { >
if (option) {options.map((option, i) => {
return ( if (option)
<AntSelect.Option key={i} value={option.value}> return (
{option.name} <AntSelect.Option key={i} value={option.value}>
</AntSelect.Option> {option.name}
); </AntSelect.Option>
})} );
</AntSelect> 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 { useStatus } from 'client/hooks/useStatus';
import { useStoreValue } from 'client/hooks/useStoreValue'; 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 { Status } from 'core/types/statuses';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React from 'react'; import React from 'react';
import { DEFAULT_DEBOUNCE_DELAY } from 'core/constants/debounce';
const Switch = ({ name, valueName, computedValue }) => { const Switch = ({ name, valueName, computedValue }) => {
const { value, setCurrentValue } = useStoreValue({ const { value, setCurrentValue } = useStoreValue({
computedValue, computedValue,
valueName, valueName,
debounceDelay: DEFAULT_DEBOUNCE_DELAY debounceDelay: DEFAULT_DEBOUNCE_DELAY,
}); });
const { status } = useStatus(name); const { status } = useStatus(name);
return ( return (
<Box> <Form.Item>
<AntSwitch <AntSwitch
disabled={status === Status.Disabled} disabled={status === Status.Disabled}
checked={value} checked={value}
@ -24,7 +23,7 @@ const Switch = ({ name, valueName, computedValue }) => {
setCurrentValue(checked); setCurrentValue(checked);
}} }}
/> />
</Box> </Form.Item>
); );
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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