Compare commits

..

14 Commits

Author SHA1 Message Date
vchikalkin
0a9ba6bb84 packages/tools: fix dependencies 2022-12-20 20:23:16 +03:00
vchikalkin
2c9117d08c packages: remove turbo 2022-12-20 20:14:44 +03:00
vchikalkin
efdad41493 repo: rename packages to @packages 2022-12-20 20:08:55 +03:00
vchikalkin
8e48a48fee packages/ui: add @ant-design/icons 2022-12-20 19:56:59 +03:00
vchikalkin
ec7f7ea941 package.json: add script graphql:update 2022-12-20 19:53:41 +03:00
vchikalkin
95bd5d1010 Revert "repo: move ./graphql to packages/graphql (gql)"
This reverts commit 74af4fb4922cf84f297807b25c19a1ae9f40a9b5.
2022-12-20 19:48:13 +03:00
vchikalkin
09ba9499a3 package.json: revert dev & build scripts 2022-12-20 13:04:41 +03:00
vchikalkin
0f425109a6 packages: fix mobx types errors 2022-12-20 13:00:22 +03:00
vchikalkin
74af4fb492 repo: move ./graphql to packages/graphql (gql) 2022-12-20 12:53:55 +03:00
vchikalkin
f451af545a repo: move elements to packages/ui 2022-12-20 12:01:31 +03:00
vchikalkin
e748a560ed repo: remove tsconfig.json from packages 2022-12-20 11:53:10 +03:00
vchikalkin
b4b6192f1c repo: move tools to packages/tools 2022-12-20 11:42:59 +03:00
vchikalkin
eff67858dc repo: move UIKit to packages/ui 2022-12-20 11:33:18 +03:00
vchikalkin
7b9dbdaeb5 add turborepo
move ./Elements to packages/elements
2022-12-19 19:08:32 +03:00
607 changed files with 25856 additions and 53677 deletions

View File

@ -1,8 +1,7 @@
.git
Dockerfile
.dockerignore
node_modules
*.log
dist
npm-debug.log
README.md
.next
README.md
.git

2
.env Normal file
View File

@ -0,0 +1,2 @@
####### USERS ########
USERS_SUPER=["akalinina","vchikalkin"]

View File

@ -5,8 +5,5 @@ yarn.lock
package-lock.json
**/*.test.js
coverage
.eslintrc.js
**/*.config.js
**/scripts
**/package.json
turbo.json
mocks
graphql

View File

@ -1,11 +0,0 @@
module.exports = {
root: true,
settings: {
next: {
rootDir: ['apps/*/'],
},
react: {
version: 'detect',
},
},
};

89
.eslintrc.json Normal file
View File

@ -0,0 +1,89 @@
{
"root": true,
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"next",
"prettier",
"airbnb",
"airbnb-typescript",
"plugin:@typescript-eslint/recommended",
"plugin:unicorn/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"parser": "@typescript-eslint/parser",
"plugins": ["react", "prettier", "@typescript-eslint", "unicorn", "testing-library"],
"rules": {
"linebreak-style": ["error", "windows"],
"comma-dangle": "off",
"@typescript-eslint/comma-dangle": ["off"],
"react/react-in-jsx-scope": "off",
"react/jsx-props-no-spreading": "off",
"react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }],
"react/forbid-prop-types": "off",
"react/require-default-props": [
"error",
{
"ignoreFunctionalComponents": true
}
],
"import/extensions": "off",
"object-curly-newline": [
"warn",
{
"ObjectExpression": "always",
"ObjectPattern": { "multiline": true },
"ImportDeclaration": "never",
"ExportDeclaration": { "multiline": true, "minProperties": 3 }
}
],
"lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": ["off"],
"indent": "off",
"@typescript-eslint/indent": ["off"],
"react/jsx-no-bind": [
"error",
{
"ignoreDOMComponents": false,
"ignoreRefs": false,
"allowArrowFunctions": false,
"allowFunctions": true,
"allowBind": false
}
],
"newline-before-return": "warn",
"@typescript-eslint/consistent-type-imports": "error",
"react/prop-types": "off",
// Airbnb prefers forEach
"unicorn/no-array-for-each": "off",
"unicorn/prevent-abbreviations": "off",
"unicorn/no-null": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/no-array-reduce": "off",
"unicorn/prefer-module": "off",
"unicorn/text-encoding-identifier-case": "off",
"unicorn/filename-case": [
"error",
{
"case": "kebabCase",
"ignore": ["^.*.(jsx|tsx)$"]
}
],
"import/no-unresolved": "warn",
"implicit-arrow-linebreak": "warn",
"operator-linebreak": "warn",
"function-paren-newline": "warn"
},
"overrides": [
// Only uses Testing Library lint rules in test files
{
"files": ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
"extends": ["plugin:testing-library/react"]
}
]
}

47
.gitignore vendored
View File

@ -1,37 +1,12 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Created by https://www.toptal.com/developers/gitignore/api/nextjs
# Edit at https://www.toptal.com/developers/gitignore?templates=nextjs
### NextJS ###
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
/coverage
# next.js
.next/
@ -59,14 +34,14 @@ yarn-error.log*
# typescript
*.tsbuildinfo
# End of https://www.toptal.com/developers/gitignore/api/nextjs
# Created by https://www.toptal.com/developers/gitignore/api/turbo
# Edit at https://www.toptal.com/developers/gitignore?templates=turbo
# Yarn
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
### Turbo ###
# Turborepo task cache
# turbo
.turbo
# End of https://www.toptal.com/developers/gitignore/api/turbo
.pnpm

View File

@ -1,12 +1,11 @@
overwrite: true
schema: './graphql/crm.schema.graphql'
documents: [./**/!(*.schema).graphql]
documents: '**/*.{graphql,js,ts,jsx,tsx}'
generates:
./graphql/crm.types.ts:
plugins:
- typescript
- typescript-operations
- typed-document-node
config:
onlyOperationTypes: true
useTypeImports: true
@ -16,7 +15,7 @@ generates:
object: true
defaultValue: true
scalars:
UUID: string
Uuid: string
Decimal: number
DateTime: string
# exclude: './graphql/crm.schema.graphql'

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged --concurrent false
yarn precommit

View File

@ -1,5 +1,3 @@
.next
public
**/graphql/*.types.ts
**/graphql/*.schema.graphql
node_modules
graphql

View File

@ -4,7 +4,7 @@
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"insertPragma": false,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",

10
.vscode/launch.json vendored
View File

@ -9,15 +9,9 @@
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"type": "pwa-chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/apps/web",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack://_N_E/*": "${webRoot}/*"
},
"skipFiles": ["**/<node_internals>/**", "**/node_modules/**"]
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",

18
.vscode/settings.json vendored
View File

@ -13,19 +13,9 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.removeUnusedImports": "explicit"
"source.organizeImports": true,
"source.fixAll.eslint": true,
"source.fixAll.format": true
},
"workbench.editor.labelFormat": "short",
"eslint.workingDirectories": [{ "directory": "apps/web", "changeProcessCWD": true }],
"eslint.validate": [
"javascript",
"javascriptreact",
"json",
"typescript",
"typescriptreact",
"yaml"
],
"eslint.lintTask.enable": true
"workbench.editor.labelFormat": "short"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

807
.yarn/releases/yarn-3.3.0.cjs vendored Normal file

File diff suppressed because one or more lines are too long

9
.yarnrc.yml Normal file
View File

@ -0,0 +1,9 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: '@yarnpkg/plugin-typescript'
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
spec: 'https://mskelton.dev/yarn-outdated/v3'
yarnPath: .yarn/releases/yarn-3.3.0.cjs

View File

@ -1,8 +1,8 @@
export function areEqual<T>(arr1: readonly T[], arr2: readonly T[]) {
export function areEqual<T>(arr1: ReadonlyArray<T>, arr2: ReadonlyArray<T>) {
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
}
export function difference<T>(arr1: readonly T[], arr2: readonly T[]) {
export function difference<T>(arr1: ReadonlyArray<T>, arr2: ReadonlyArray<T>) {
if (arr1.length !== arr2.length) return null;
const changes = [];
@ -16,6 +16,6 @@ export function difference<T>(arr1: readonly T[], arr2: readonly T[]) {
return changes;
}
export function isSorted(arr: number[]) {
export function isSorted(arr: Array<number>) {
return arr.every((value, index, array) => !index || array[index - 1] <= value);
}

View File

@ -0,0 +1,7 @@
/* eslint-disable import/prefer-default-export */
import { isEmpty } from 'radash';
import type { BaseOption } from 'ui/elements/types';
export function normalizeOptions(options: any[] | null | undefined) {
return (isEmpty(options) ? [] : options) as BaseOption[];
}

23
@packages/tools/mobx.ts Normal file
View File

@ -0,0 +1,23 @@
/* eslint-disable import/prefer-default-export */
import type { IReactionDisposer } from 'mobx';
import { autorun } from 'mobx';
export function makeDisposable(
createReaction: () => IReactionDisposer,
mustBeDisposed: () => boolean
) {
let disposer: IReactionDisposer | undefined;
function cleanDisposer() {
disposer = undefined;
}
autorun(() => {
if (mustBeDisposed()) {
if (disposer !== undefined) disposer();
cleanDisposer();
} else {
disposer = createReaction();
}
});
}

18
@packages/tools/number.ts Normal file
View File

@ -0,0 +1,18 @@
/* eslint-disable implicit-arrow-linebreak */
export function parser(value?: string) {
if (!value) return 0;
const normalized = value.replace(/\s/g, '').replaceAll(',', '.');
return Number.parseFloat(normalized);
}
export const formatter = (value?: number) =>
Intl.NumberFormat('ru', {
minimumFractionDigits: 2,
}).format(value || 0);
export const formatterExtra = (value?: number) =>
Intl.NumberFormat('ru', {
minimumFractionDigits: 2,
maximumFractionDigits: 6,
}).format(value || 0);

View File

@ -1,3 +1,4 @@
/* eslint-disable import/prefer-default-export */
export function flatten(obj: object) {
return Object.values(obj).flat();
}

View File

@ -0,0 +1,12 @@
{
"name": "tools",
"version": "0.0.0",
"main": "./index.tsx",
"types": "./index.tsx",
"license": "MIT",
"dependencies": {
"mobx": "^6.7.0",
"radash": "^10.3.0",
"ui": "*"
}
}

11
@packages/ui/colors.js Normal file
View File

@ -0,0 +1,11 @@
/* eslint-disable import/prefer-default-export */
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
:root {
--color-background: rgb(240, 240, 240);
--color-primary: ${process.env.NEXT_PUBLIC_COLOR_PRIMARY};
--color-secondary: ${process.env.NEXT_PUBLIC_COLOR_SECONDARY};
--color-tertiarty: ${process.env.NEXT_PUBLIC_COLOR_TERTIARTY};
}
`;

View File

@ -0,0 +1,3 @@
/* eslint-disable unicorn/filename-case */
/* eslint-disable no-restricted-exports */
export { Alert as default } from 'antd';

View File

@ -0,0 +1,34 @@
import { Button as AntButton } from 'antd';
import type { BaseButtonProps } from 'antd/lib/button/button';
import type { FC } from 'react';
import { useThrottledCallback } from 'use-debounce';
import type { BaseElementProps } from './types';
type ElementProps = {
action: () => void;
text: string;
};
type ButtonProps = BaseButtonProps & Pick<ElementProps, 'text'>;
export default (function Button({
status,
action,
text,
...props
}: BaseElementProps<never> & ElementProps) {
const throttledAction = useThrottledCallback(action, 1200, {
trailing: false,
});
return (
<AntButton
disabled={status === 'Disabled'}
loading={status === 'Loading'}
onClick={throttledAction}
{...props}
>
{text}
</AntButton>
);
} as FC<ButtonProps>);

View File

@ -0,0 +1,40 @@
import type { CheckboxProps as AntCheckboxProps } from 'antd';
import { Checkbox as AntCheckbox, Form } from 'antd';
import type { CheckboxChangeEvent } from 'antd/lib/checkbox';
import type { FC } from 'react';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
type ElementProps = {
text: string;
};
type CheckboxProps = AntCheckboxProps & ElementProps;
export default (function Checkbox({
value,
setValue,
status,
isValid,
help,
text,
...props
}: BaseElementProps<boolean> & ElementProps) {
function handleChange(e: CheckboxChangeEvent) {
setValue(e.target.checked);
}
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntCheckbox
checked={value}
onChange={handleChange}
disabled={status === 'Disabled'}
{...props}
>
{text}
</AntCheckbox>
</FormItem>
);
} as FC<CheckboxProps>);

View File

@ -0,0 +1,25 @@
import type { InputProps } from 'antd';
import { Form, Input as AntInput } from 'antd';
import type { ChangeEvent, FC } from 'react';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
export default (function Input({
value,
setValue,
status,
isValid,
help,
...props
}: BaseElementProps<string>) {
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntInput value={value} onChange={handleChange} disabled={status === 'Disabled'} {...props} />
</FormItem>
);
} as FC<InputProps>);

View File

@ -0,0 +1,29 @@
import type { InputNumberProps as AntInputNumberProps } from 'antd';
import { Form, InputNumber as AntInputNumber } from 'antd';
import type { FC } from 'react';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
type InputNumberProps = AntInputNumberProps<number>;
export default (function InputNumber({
setValue,
status,
isValid,
help,
...props
}: BaseElementProps<number>) {
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntInputNumber
onChange={setValue}
disabled={status === 'Disabled'}
style={{
width: '100%',
}}
{...props}
/>
</FormItem>
);
} as FC<InputNumberProps>);

View File

@ -0,0 +1,30 @@
import { Button as AntButton } from 'antd';
import type { BaseButtonProps } from 'antd/lib/button/button';
import type { FC } from 'react';
import type { BaseElementProps } from './types';
type ElementProps = {
text: string;
};
type LinkProps = BaseButtonProps & ElementProps;
export default (function Link({
value,
status,
text,
...props
}: BaseElementProps<string> & ElementProps) {
return (
<AntButton
rel="noopener"
target="_blank"
href={value}
disabled={status === 'Disabled' || !value}
loading={status === 'Loading'}
{...props}
>
{text}
</AntButton>
);
} as FC<LinkProps>);

View File

@ -0,0 +1,49 @@
import type { RadioChangeEvent, RadioGroupProps, SpaceProps } from 'antd';
import { Form, Radio as AntRadio, Space } from 'antd';
import type { FC } from 'react';
import type { BaseElementProps, BaseOption } from './types';
const { Item: FormItem } = Form;
type ElementProps = BaseElementProps<string | null> & {
options: BaseOption[];
spaceProps?: SpaceProps;
};
type RadioProps = RadioGroupProps & {
spaceProps?: SpaceProps;
};
export default (function Radio({
value = null,
setValue,
options,
status,
isValid,
help,
spaceProps,
...props
}: ElementProps) {
function handleChange(e: RadioChangeEvent) {
setValue(e.target.value);
}
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntRadio.Group
value={value}
onChange={handleChange}
disabled={status === 'Disabled'}
{...props}
>
<Space {...spaceProps}>
{options.map((option) => (
<AntRadio key={option.value} value={option.value}>
{option.label}
</AntRadio>
))}
</Space>
</AntRadio.Group>
</FormItem>
);
} as FC<RadioProps>);

View File

@ -0,0 +1,3 @@
/* eslint-disable unicorn/filename-case */
/* eslint-disable no-restricted-exports */
export { Result as default } from 'antd';

View File

@ -0,0 +1,32 @@
import type { SegmentedProps } from 'antd';
import { Form, Segmented as AntSegmented } from 'antd';
import type { FC } from 'react';
import type { BaseElementProps, BaseOption } from './types';
const { Item: FormItem } = Form;
type ElementProps = BaseElementProps<string | number> & {
options: BaseOption[];
};
export default (function Segmented({
value,
setValue,
options,
status,
isValid,
help,
...props
}: ElementProps) {
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntSegmented
value={value}
onChange={setValue}
disabled={status === 'Disabled'}
options={options}
{...props}
/>
</FormItem>
);
} as FC<Partial<SegmentedProps>>);

View File

@ -0,0 +1,46 @@
import type { SelectProps } from 'antd';
import { Form, Select as AntSelect } from 'antd';
import type { FC } from 'react';
import { useMemo } from 'react';
import type { BaseElementProps, BaseOption } from './types';
const { Item: FormItem } = Form;
type ElementProps = {
options: BaseOption[];
};
export default (function Select({
value = null,
setValue,
options = [],
status,
isValid,
help,
...props
}: BaseElementProps<string | null> & ElementProps) {
const optionsWithNull = useMemo(
() => [
{
label: 'Не выбрано',
value: null,
},
...options,
],
[options]
);
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntSelect
value={value}
onChange={setValue}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
optionFilterProp="children"
options={optionsWithNull}
{...props}
/>
</FormItem>
);
} as FC<SelectProps>);

View File

@ -0,0 +1,21 @@
import type { SwitchProps } from 'antd';
import { Form, Switch as AntSwitch } from 'antd';
import type { FC } from 'react';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
export default (function Switch({
value,
setValue,
status,
isValid,
help,
...props
}: BaseElementProps<boolean>) {
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<AntSwitch checked={value} onChange={setValue} disabled={status === 'Disabled'} {...props} />
</FormItem>
);
} as FC<SwitchProps>);

View File

@ -0,0 +1,4 @@
/* eslint-disable unicorn/filename-case */
/* eslint-disable no-restricted-exports */
export { Table as default } from 'antd';

View File

@ -0,0 +1,16 @@
import type { FC, ReactNode } from 'react';
import styled from 'styled-components';
const Span = styled.span`
margin-bottom: 18px;
font-size: 0.85rem;
`;
type TextProps = {
value: any;
children: ReactNode;
};
export default (function Text({ value, ...props }: TextProps) {
return <Span {...props}>{value || props.children}</Span>;
} as FC<TextProps>);

View File

@ -0,0 +1,3 @@
/* eslint-disable unicorn/filename-case */
/* eslint-disable no-restricted-exports */
export { Tooltip as default } from 'antd';

View File

@ -0,0 +1,4 @@
/* eslint-disable unicorn/filename-case */
export { ConfigProvider } from 'antd';
export { default as ru_RU } from 'antd/lib/locale/ru_RU';

View File

@ -0,0 +1,3 @@
import DownloadOutlined from '@ant-design/icons/lib/icons/DownloadOutlined';
export default <DownloadOutlined />;

View File

@ -0,0 +1,15 @@
export { default as Button } from './Button';
export { default as Checkbox } from './Checkbox';
export { default as Input } from './Input';
export { default as InputNumber } from './InputNumber';
export { default as Link } from './Link';
export { default as message } from './message';
export { default as notification } from './notification';
export { default as Radio } from './Radio';
export { default as Result } from './Result';
export { default as Segmented } from './Segmented';
export { default as Select } from './Select';
export { default as Switch } from './Switch';
export { default as Table } from './Table';
export { default as Text } from './Text';
export { default as Tooltip } from './Tooltip';

View File

@ -1,6 +1,6 @@
import { min } from '@/styles/mq';
import styled from 'styled-components';
import { Box } from 'ui/grid';
import { min } from 'ui/mq';
const Background = styled(Box)`
background: #fff;

View File

@ -0,0 +1,4 @@
/* eslint-disable unicorn/filename-case */
/* eslint-disable no-restricted-exports */
export { Divider as default } from 'antd';

View File

@ -0,0 +1,4 @@
/* eslint-disable unicorn/filename-case */
/* eslint-disable no-restricted-exports */
export { Tabs as default } from 'antd';

View File

@ -0,0 +1,9 @@
/* eslint-disable unicorn/prefer-export-from */
import { message } from 'antd';
message.config({
top: 70,
maxCount: 3,
});
export default message;

View File

@ -0,0 +1,8 @@
/* eslint-disable unicorn/prefer-export-from */
import { notification } from 'antd';
notification.config({
placement: 'bottomRight',
});
export default notification;

View File

@ -0,0 +1 @@
@import 'antd/dist/antd.less';

View File

@ -0,0 +1,14 @@
export type Status = 'Default' | 'Disabled' | 'Loading' | 'Hidden';
export type BaseElementProps<Value> = {
value: Value;
setValue: (value: Value) => void;
status?: Status;
isValid?: boolean;
help?: string;
};
export type BaseOption<Value = any> = {
label: string;
value: Value;
};

View File

@ -1,2 +1,2 @@
export type { BoxProps, FlexProps } from 'rebass/styled-components';
export { Box, Flex } from 'rebass/styled-components';
export type { BoxProps, FlexProps } from 'rebass/styled-components';

18
@packages/ui/mq.ts Normal file
View File

@ -0,0 +1,18 @@
const screens = {
tablet: 768,
laptop: 1024,
'laptop-hd': 1280,
desktop: 1680,
'desktop-xl': 1921,
};
const threshold = 0;
export function min(breakpoint: keyof typeof screens) {
return `@media (min-width: calc(${screens[breakpoint]}px + ${threshold}px))`;
}
export function max(breakpoint: keyof typeof screens) {
return `@media (max-width: calc(${screens[breakpoint]}px))`;
}
export const breakpoints = Object.values(screens).map((value) => `${value + threshold}px`);

20
@packages/ui/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "ui",
"version": "0.0.0",
"main": "./index.tsx",
"types": "./index.tsx",
"license": "MIT",
"devDependencies": {
"@types/react": "18.0.20",
"@types/rebass": "^4.0.10",
"@types/styled-components": "^5"
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"antd": "4.24.5",
"react": "^18.2.0",
"rebass": "^4.0.7",
"styled-components": "^5.3.6",
"use-debounce": "^9.0.2"
}
}

7
@packages/ui/theme.js Normal file
View File

@ -0,0 +1,7 @@
import { breakpoints } from './mq';
const theme = {
breakpoints,
};
export default theme;

View File

@ -1,3 +1,4 @@
/* eslint-disable object-curly-newline */
import type { FormTabRows } from '../../lib/render-rows';
export const id = 'add-product';
@ -7,7 +8,7 @@ export const rows: FormTabRows = [
{
title: 'Регистрация',
},
[['radioObjectRegistration', 'radioTypePTS'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['radioObjectRegistration', 'radioTypePTS'], { gridTemplateColumns: ['1fr 1fr'] }],
[['selectRegionRegistration', 'selectTownRegistration', 'selectObjectRegionRegistration']],
[['selectObjectCategoryTax', 'selectObjectTypeTax', 'tbxVehicleTaxInYear']],
[['tbxLeaseObjectYear', 'tbxLeaseObjectMotorPower', 'tbxVehicleTaxInLeasingPeriod']],
@ -26,6 +27,6 @@ export const rows: FormTabRows = [
{
title: 'Доп. продукты',
},
[['selectTechnicalCard', 'selectInsNSIB'], { gridTemplateColumns: ['1fr', '1fr 2fr'] }],
[['selectTechnicalCard', 'selectInsNSIB'], { gridTemplateColumns: '1fr 2fr' }],
[['selectRequirementTelematic', 'selectTracker', 'selectTelematic']],
];

View File

@ -6,7 +6,7 @@ function Insurance() {
}
export default {
Component: Insurance,
id,
title,
Component: Insurance,
};

View File

@ -0,0 +1,13 @@
/* eslint-disable object-curly-newline */
import type { FormTabRows } from '../../lib/render-rows';
export const id = 'create-kp';
export const title = 'Создание КП';
export const rows: FormTabRows = [
[['cbxPriceWithDiscount', 'cbxFullPriceWithDiscount', 'cbxCostIncrease']],
[['cbxInsurance', 'cbxRegistrationQuote', 'cbxTechnicalCardQuote']],
[['cbxNSIB', 'cbxQuoteRedemptionGraph', 'cbxShowFinGAP']],
[['tbxQuoteName', 'radioQuoteContactGender'], { gridTemplateColumns: '1fr 1fr' }],
[['btnCreateKP', 'linkDownloadKp'], { gridTemplateColumns: '1fr 1fr' }],
];

View File

@ -6,7 +6,7 @@ function CreateKP() {
}
export default {
Component: CreateKP,
id,
title,
Component: CreateKP,
};

View File

@ -1,6 +1,6 @@
/* eslint-disable canonical/sort-keys */
import type { Risk } from './types';
/* eslint-disable import/prefer-default-export */
import type { ColumnsType } from 'antd/lib/table';
import type { Risk } from './types';
export const columns: ColumnsType<Risk> = [
{

View File

@ -1,48 +1,41 @@
import { columns } from './config';
import { useStore } from '@/stores/hooks';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
import { Alert, Table } from 'ui/elements';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Flex } from 'ui/grid';
import { columns } from './config';
const Grid = styled(Flex)`
flex-direction: column;
`;
const Validation = observer(() => {
const { $tables, $process } = useStore();
const store = useStore();
const errors = $tables.fingap.validation.getErrors();
const messages = store.$tables.fingap.validation.getMessages();
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
if (messages?.length) {
return <Alert type="error" banner message={messages[0]} />;
}
return null;
});
const FinGAP = observer(() => {
const { $tables, $process } = useStore();
const FinGAPTable = observer(() => {
const { $tables } = useStore();
const { fingap } = $tables;
const dataSource = toJS(fingap.risks);
const selectedRowKeys = [...toJS(fingap.selectedKeys)];
const disabled = $process.has('LoadKP') || $process.has('Calculate') || $process.has('CreateKP');
return (
<Table
columns={columns}
dataSource={dataSource}
rowSelection={{
getCheckboxProps: () => ({ disabled }),
type: 'checkbox',
onChange: (_, selectedRows) => {
const selectedKeys = selectedRows.reduce((acc, row) => {
acc.push(row.key);
@ -54,7 +47,6 @@ const FinGAP = observer(() => {
fingap.setSelectedKeys(selectedKeys);
},
selectedRowKeys,
type: 'checkbox',
}}
pagination={false}
size="small"
@ -65,11 +57,11 @@ const FinGAP = observer(() => {
);
});
export default function FinGAPTable() {
export default function () {
return (
<Grid>
<Validation />
<FinGAP />
<FinGAPTable />
</Grid>
);
}

View File

@ -1,4 +1,4 @@
import type { RiskSchema } from '@/config/schema/fingap';
import type { RiskSchema } from 'config/schema/fingap';
import type { z } from 'zod';
export type Risk = z.infer<typeof RiskSchema>;

View File

@ -1,8 +1,8 @@
import { useInsuranceValue } from './hooks';
import type { Values } from './types';
import { useRow } from '@/stores/tables/insurance/hooks';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useRow } from 'stores/tables/insurance/hooks';
import { useInsuranceValue } from './hooks';
import type { Values } from './types';
export function buildOptionComponent<T>(
key: string,
@ -13,17 +13,10 @@ export function buildOptionComponent<T>(
const [value, setValue] = useInsuranceValue(key, valueName);
const { getOptions, getStatus } = useRow(key);
const options = getOptions(valueName);
const status = getStatus(valueName);
const statuses = getStatus(valueName);
return (
<Component
options={options}
onChange={setValue}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
value={value}
{...props}
/>
<Component value={value} options={options} setValue={setValue} status={statuses} {...props} />
);
});
}
@ -36,10 +29,8 @@ export function buildValueComponent<T>(
return observer((props: T) => {
const [value, setValue] = useInsuranceValue(key, valueName);
const { getStatus } = useRow(key);
const status = getStatus(valueName);
const statuses = getStatus(valueName);
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
return <Component value={value} setValue={setValue} status={statuses} {...props} />;
});
}

View File

@ -0,0 +1,67 @@
/* eslint-disable import/prefer-default-export */
import type { ColumnsType } from 'antd/lib/table';
import { MAX_INSURANCE } from 'constants/values';
import { formatter, parser } from 'tools/number';
import InputNumber from 'ui/elements/InputNumber';
import Select from 'ui/elements/Select';
import { buildOptionComponent, buildValueComponent } from './builders';
import type * as Insurance from './types';
export const columns: ColumnsType<Insurance.RowValues> = [
{
key: 'policyType',
dataIndex: 'policyType',
title: 'Тип полиса',
},
{
key: 'insuranceCompany',
dataIndex: 'insuranceCompany',
title: 'Страховая компания',
width: 300,
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insuranceCompany');
return <Component showSearch optionFilterProp="label" />;
},
},
{
key: 'insured',
dataIndex: 'insured',
title: 'Плательщик',
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insured');
return <Component />;
},
},
{
key: 'insCost',
dataIndex: 'insCost',
title: 'Стоимость за 1-й период',
render: (_, record) => {
const Component = buildValueComponent(record.key, InputNumber, 'insCost');
return (
<Component
min={0}
max={MAX_INSURANCE}
step={1000}
precision={2}
parser={parser}
formatter={formatter}
addonAfter="₽"
/>
);
},
},
{
key: 'insTerm',
dataIndex: 'insTerm',
title: 'Срок страхования',
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insTerm');
return <Component />;
},
},
];

View File

@ -1,5 +1,6 @@
import { useRow } from '@/stores/tables/insurance/hooks';
/* eslint-disable import/prefer-default-export */
import { useEffect, useState } from 'react';
import { useRow } from 'stores/tables/insurance/hooks';
import { useDebouncedCallback } from 'use-debounce';
export function useInsuranceValue(key, valueName) {
@ -7,19 +8,24 @@ export function useInsuranceValue(key, valueName) {
const storeValue = row.getValue(valueName);
function setStoreValue(val) {
return row.setValue(valueName, val);
function setStoreValue(value) {
return row.setValue(valueName, value);
}
const [value, setValue] = useState(storeValue);
// eslint-disable-next-line object-curly-newline
const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, 350, { maxWait: 1000 });
useEffect(() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
}, [value]);
useEffect(
() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[value]
);
useEffect(() => {
setValue(storeValue);

View File

@ -1,9 +1,10 @@
import { columns } from './config';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
import { Alert, Table } from 'ui/elements';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Flex } from 'ui/grid';
import { columns } from './config';
const Grid = styled(Flex)`
flex-direction: column;
@ -16,24 +17,18 @@ const TableWrapper = styled.div`
`;
const Validation = observer(() => {
const { $tables, $process } = useStore();
const store = useStore();
const errors = $tables.insurance.validation.getErrors();
const messages = store.$tables.insurance.validation.getMessages();
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
if (messages?.length) {
return <Alert type="error" banner message={messages[0]} />;
}
return null;
});
const Insurance = observer(() => {
const InsuranceTable = observer(() => {
const store = useStore();
const { values } = store.$tables.insurance;
@ -53,11 +48,11 @@ const Insurance = observer(() => {
);
});
export default function InsuranceTable() {
export default function () {
return (
<Grid>
<Validation />
<Insurance />
<InsuranceTable />
</Grid>
);
}

View File

@ -1,6 +1,5 @@
import type { KeysSchema, RowSchema } from '@/config/schema/insurance';
import type { Status } from '@/stores/calculation/statuses/types';
import type { BaseOption } from 'ui/elements/types';
import type { KeysSchema, RowSchema } from 'config/schema/insurance';
import type { BaseOption, Status } from 'ui/elements/types';
import type { z } from 'zod';
export type Keys = z.infer<typeof KeysSchema>;
@ -10,7 +9,7 @@ export type RowValues = z.infer<typeof RowSchema>;
export type Values = Exclude<keyof RowValues, 'key'>;
export type RowOptions = {
[ValueName in Values]: Array<BaseOption<RowValues[ValueName]>>;
[ValueName in Values]?: BaseOption<RowValues[ValueName]>[];
};
export type RowStatuses = Record<Values, Status>;

View File

@ -0,0 +1,12 @@
import type { FormTabRows } from '../../lib/render-rows';
export const id = 'insurance';
export const title = 'Страхование';
export const rows: FormTabRows = [
[['tbxLeaseObjectYear', 'selectLeaseObjectUseFor', 'selectLegalClientRegion']],
[['selectEngineType', 'tbxInsFranchise', 'selectLegalClientTown']],
[['selectLeaseObjectCategory', 'tbxMileage', 'tbxINNForCalc']],
[['tbxLeaseObjectMotorPower', 'cbxWithTrailer', 'selectGPSBrand']],
[['tbxEngineVolume', 'cbxInsDecentral', 'selectGPSModel']],
];

View File

@ -1,15 +1,15 @@
import { Flex } from 'ui/grid';
import renderFormRows from '../../lib/render-rows';
import { id, mobileRows, rows, title } from './config';
import { id, rows, title } from './config';
import FinGAPTable from './FinGAPTable';
import InsuranceTable from './InsuranceTable';
import { Media } from '@/styles/media';
import { Flex } from 'ui/grid';
function Insurance() {
const renderedRows = renderFormRows(rows);
return (
<Flex flexDirection="column">
<Media lessThan="laptop">{renderFormRows(mobileRows)}</Media>
<Media greaterThanOrEqual="laptop">{renderFormRows(rows)}</Media>
{renderedRows}
<InsuranceTable />
<FinGAPTable />
</Flex>
@ -17,7 +17,7 @@ function Insurance() {
}
export default {
Component: Insurance,
id,
title,
Component: Insurance,
};

View File

@ -1,3 +1,4 @@
/* eslint-disable object-curly-newline */
import type { FormTabRows } from '../../lib/render-rows';
export const id = 'leasing';
@ -8,8 +9,8 @@ export const rows: FormTabRows = [
[['tbxLeaseObjectPrice', 'tbxVATInLeaseObjectPrice', 'tbxLeaseObjectPriceWthtVAT']],
[['selectSupplierCurrency', 'tbxSupplierDiscountRub', 'tbxSupplierDiscountPerc']],
[['tbxFirstPaymentPerc', 'tbxFirstPaymentRub']],
[['tbxLeasingPeriod', 'tbxSaleBonus']],
[['selectSubsidy', 'tbxSubsidySum'], { gridTemplateColumns: ['1fr', '2fr 1fr'] }],
[['tbxLeasingPeriod', 'tbxSaleBonus', 'tbxRedemptionPaymentSum']],
[['selectSubsidy', 'tbxSubsidySum'], { gridTemplateColumns: '2fr 1fr' }],
[['selectImportProgram', 'tbxImportProgramSum', 'tbxAddEquipmentPrice']],
[['radioLastPaymentRule'], { gridTemplateColumns: '1fr' }],
[['tbxLastPaymentPerc', 'tbxLastPaymentRub']],

View File

@ -6,7 +6,7 @@ function Leasing() {
}
export default {
Component: Leasing,
id,
title,
Component: Leasing,
};

View File

@ -1,5 +1,5 @@
/* eslint-disable object-curly-newline */
import type { FormTabRows } from '../../lib/render-rows';
import { transformRowsForMobile } from '../lib/utils';
export const id = 'leasing-object';
export const title = 'ПЛ';
@ -15,7 +15,5 @@ export const rows: FormTabRows = [
[['tbxLeaseObjectCount', 'selectEngineType', 'tbxMaxSpeed']],
[['tbxLeaseObjectYear', 'tbxLeaseObjectMotorPower', 'tbxCountSeats']],
[['selectLeaseObjectCategory', 'tbxEngineVolume', 'tbxMileage']],
[['tbxMaxMass', 'tbxEngineHours', 'tbxVIN']],
[['tbxMaxMass', 'tbxEngineHours']],
];
export const mobileRows = transformRowsForMobile(rows);

View File

@ -0,0 +1,12 @@
import renderFormRows from '../../lib/render-rows';
import { id, rows, title } from './config';
function LeasingObject() {
return renderFormRows(rows);
}
export default {
id,
title,
Component: LeasingObject,
};

View File

@ -1,8 +1,8 @@
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import { Flex } from 'ui/grid';
import elementsRender from '../../config/elements-render';
import { elements } from './config';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Flex } from 'ui/grid';
function PaymentsParams() {
const renderedElements = elements.map((elementName) => {
@ -17,9 +17,8 @@ function PaymentsParams() {
const graphType = $calculation.element('radioGraphType').getValue();
switch (graphType) {
case 100_000_000: {
case 100_000_000:
return null;
}
case 100_000_001: {
return <Flex flexDirection="column">{selectSeasonType}</Flex>;
}
@ -38,9 +37,8 @@ function PaymentsParams() {
return null;
}
default: {
default:
break;
}
}
}

View File

@ -0,0 +1,14 @@
/* eslint-disable import/prefer-default-export */
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useRowStatus } from 'stores/tables/payments/hooks';
import { usePaymentValue } from './hooks';
export function buildValueComponent<T>(index: number, Component: ComponentType<T>) {
return observer((props: T) => {
const [value, setValue] = usePaymentValue(index);
const status = useRowStatus(index);
return <Component value={value} setValue={setValue} status={status} {...props} />;
});
}

View File

@ -0,0 +1,38 @@
/* eslint-disable import/prefer-default-export */
import type { ColumnsType } from 'antd/lib/table';
import InputNumber from 'ui/elements/InputNumber';
import { buildValueComponent } from './builders';
type Payment = {
key: number;
num: number;
paymentRelation: number;
};
export const columns: ColumnsType<Payment> = [
{
key: 'num',
dataIndex: 'num',
title: '#',
width: '7%',
render: (_value, payment) => payment.num + 1,
},
{
key: 'paymentRelation',
dataIndex: 'paymentRelation',
title: '% платежа',
render: (_value, payment) => {
const Component = buildValueComponent(payment.num, InputNumber);
return (
<Component
min={payment.num === 0 ? 0 : 0.01}
max={100}
step={1}
precision={payment.num === 0 ? 4 : 2}
/>
);
},
},
];

View File

@ -0,0 +1,28 @@
/* eslint-disable import/prefer-default-export */
import { useEffect, useState } from 'react';
import { useRowValue } from 'stores/tables/payments/hooks';
import { useDebouncedCallback } from 'use-debounce';
export function usePaymentValue(index) {
const [storeValue, setStoreValue] = useRowValue(index);
const [value, setValue] = useState(storeValue);
// eslint-disable-next-line object-curly-newline
const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, 350, { maxWait: 1000 });
useEffect(
() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[value]
);
useEffect(() => {
setValue(storeValue);
}, [storeValue]);
return [value, setValue];
}

View File

@ -0,0 +1,89 @@
import { computed } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Box, Flex } from 'ui/grid';
import { min } from 'ui/mq';
import { columns } from './config';
const Grid = styled(Flex)`
flex-direction: column;
`;
const TableWrapper = styled.div`
td > * {
margin: 0;
}
`;
const TablesGroupGrid = styled(Box)`
display: flex;
flex-direction: column;
gap: 10px;
${min('tablet')} {
display: grid;
grid-template-columns: repeat(5, 1fr);
}
`;
const Validation = observer(() => {
const store = useStore();
const { payments } = store.$tables;
const messages = payments.validation.getMessages();
if (messages?.length) {
return <Alert type="error" banner message={messages[0]} />;
}
return null;
});
const SPLIT_NUMBER = 12;
function TablePart({ num }) {
const store = useStore();
const { payments } = store.$tables;
const values = payments.values.slice(num * SPLIT_NUMBER, num * SPLIT_NUMBER + SPLIT_NUMBER);
const dataSource = values.map((value, index) => ({
key: index + num * SPLIT_NUMBER,
num: index + num * SPLIT_NUMBER,
paymentRelation: value,
}));
return (
<TableWrapper>
<Table size="small" columns={columns} dataSource={dataSource} pagination={false} />
</TableWrapper>
);
}
const TablesGroup = observer(() => {
const store = useStore();
const { payments } = store.$tables;
const valuesLength = computed(() => payments.values.length).get();
const tablesNumber = Math.ceil(valuesLength / SPLIT_NUMBER);
const tables = [];
for (let i = 0; i < tablesNumber; i += 1) {
tables.push(<TablePart key={i} num={i} />);
}
return <TablesGroupGrid>{tables}</TablesGroupGrid>;
});
export default function TablePayments() {
return (
<Grid>
<Validation />
<TablesGroup />
</Grid>
);
}

View File

@ -1,8 +1,8 @@
import { Box, Flex } from 'ui/grid';
import elementsRender from '../../config/elements-render';
import { id, title } from './config';
import PaymentsParams from './PaymentsParams';
import PaymentsTable from './PaymentsTable';
import { Box, Flex } from 'ui/grid';
function Payments() {
const radioGraphType = elementsRender.radioGraphType.render();
@ -12,8 +12,8 @@ function Payments() {
<Box
sx={{
display: 'grid',
gap: '10px',
gridTemplateColumns: ['1fr', '1fr', '1fr 1fr'],
gap: '10px',
}}
>
{radioGraphType}
@ -25,7 +25,7 @@ function Payments() {
}
export default {
Component: Payments,
id,
title,
Component: Payments,
};

View File

@ -1,5 +1,5 @@
/* eslint-disable object-curly-newline */
import type { FormTabRows } from '../../lib/render-rows';
import { transformRowsForMobile } from '../lib/utils';
export const id = 'supplier-agent';
export const title = 'Поставщик/агент';
@ -21,5 +21,3 @@ export const rows: FormTabRows = [
[['selectCalcBrokerRewardCondition', 'selectFinDepartmentRewardCondtion'], defaultRowStyle],
[['tbxCalcBrokerRewardSum', 'tbxFinDepartmentRewardSumm'], defaultRowStyle],
];
export const mobileRows = transformRowsForMobile(rows);

View File

@ -1,12 +1,12 @@
import renderFormRows from '../../lib/render-rows';
import { id, rows, title } from './config';
function Unlimited() {
function Leasing() {
return renderFormRows(rows);
}
export default {
Component: Unlimited,
id,
title,
Component: Leasing,
};

View File

@ -1,30 +1,16 @@
import styled from 'styled-components';
import Background from 'ui/elements/layout/Background';
import Tabs from 'ui/elements/layout/Tabs';
import { min } from 'ui/mq';
import AddProduct from './AddProduct';
import CreateKP from './CreateKP';
import ELT from './ELT';
import Insurance from './Insurance';
import Leasing from './Leasing';
import LeasingObject from './LeasingObject';
import Payments from './Payments';
import SupplierAgent from './SupplierAgent';
import Unlimited from './Unlimited';
import Background from '@/Components/Layout/Background';
import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { memo } from 'react';
import styled from 'styled-components';
import { Tabs } from 'ui/elements';
const formTabs = [
Leasing,
Payments,
LeasingObject,
SupplierAgent,
Insurance,
ELT,
AddProduct,
CreateKP,
Unlimited,
];
const formTabs = [Leasing, Payments, LeasingObject, SupplierAgent, Insurance, AddProduct, CreateKP];
const Wrapper = styled(Background)`
padding: 4px 6px;
@ -46,16 +32,11 @@ const ComponentWrapper = styled.div`
}
`;
export const Form = memo(() => {
const { $process } = useStore();
const filteredTabs =
$process.has('Unlimited') === false ? formTabs.filter((x) => x.id !== 'unlimited') : formTabs;
function Form() {
return (
<Wrapper>
<Tabs type="card" tabBarGutter="5px">
{filteredTabs.map(({ Component, id, title }) => (
{formTabs.map(({ id, title, Component }) => (
<Tabs.TabPane tab={title} key={id}>
<ComponentWrapper>
<Component />
@ -65,4 +46,6 @@ export const Form = memo(() => {
</Tabs>
</Wrapper>
);
});
}
export default Form;

View File

@ -0,0 +1,20 @@
/* eslint-disable import/prefer-default-export */
/* eslint-disable object-curly-newline */
import type { FormTabRows } from '../lib/render-rows';
const defaultRowStyle = { gridTemplateColumns: '1fr' };
export const rows: FormTabRows = [
{ title: 'Выбор Интереса/ЛС' },
[['selectLead'], defaultRowStyle],
[['selectOpportunity'], defaultRowStyle],
[['cbxRecalcWithRevision'], defaultRowStyle],
[['selectQuote'], defaultRowStyle],
[['btnCalculate'], defaultRowStyle],
{ title: 'Параметры расчета' },
[['labelIrrInfo'], defaultRowStyle],
[['radioCalcType'], defaultRowStyle],
[['tbxIRR_Perc'], defaultRowStyle],
[['tbxTotalPayments'], defaultRowStyle],
];

View File

@ -0,0 +1,21 @@
import styled from 'styled-components';
import Background from 'ui/elements/layout/Background';
import { min } from 'ui/mq';
import renderFormRows from '../lib/render-rows';
import { rows } from './config';
const Wrapper = styled(Background)`
padding: 4px 10px;
${min('tablet')} {
min-height: 790px;
}
${min('laptop')} {
padding: 4px 18px 10px;
}
`;
export default function Settings() {
return <Wrapper>{renderFormRows(rows)}</Wrapper>;
}

View File

@ -0,0 +1,32 @@
import { gql, useQuery } from '@apollo/client';
import type * as CRMTypes from 'graphql/crm.types';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
const QUERY_GET_CURRENCY_SYMBOL = gql`
query GetCurrencySymbol($currencyid: Uuid!) {
transactioncurrency(transactioncurrencyid: $currencyid) {
currencysymbol
}
}
`;
const CurrencyAddon = observer(() => {
const { $calculation } = useStore();
const currencyid = $calculation.element('selectSupplierCurrency').getValue();
const { data } = useQuery<
CRMTypes.GetCurrencySymbolQuery,
CRMTypes.GetCurrencySymbolQueryVariables
>(QUERY_GET_CURRENCY_SYMBOL, {
variables: {
currencyid: currencyid!,
},
skip: !currencyid,
});
return <span>{data?.transactioncurrency?.currencysymbol}</span>;
});
export default <CurrencyAddon />;

View File

@ -0,0 +1,27 @@
/* eslint-disable react/jsx-no-bind */
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useStatus } from 'stores/calculation/statuses/hooks';
import type { Elements } from '../config/map/actions';
type BuilderProps = {
elementName: Elements;
valueName: string;
};
export default function buildAction<T>(
Component: ComponentType<T>,
{ elementName, valueName: actionName }: BuilderProps
) {
return observer((props: T) => {
const status = useStatus(elementName);
return (
<Component
status={status}
action={() => import(`process/${actionName}`).then((m) => m.default())}
{...props}
/>
);
});
}

View File

@ -0,0 +1,37 @@
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useOptions } from 'stores/calculation/options/hooks';
import { useStatus } from 'stores/calculation/statuses/hooks';
import { useValidation } from 'stores/calculation/validation/hooks';
import type { Values } from 'stores/calculation/values/types';
import type { Elements } from '../config/map/values';
import { useStoreValue } from './hooks';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export default function buildOptions<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { isValid, help } = useValidation(elementName);
const options = useOptions(elementName);
return (
<Component
value={value}
setValue={setValue}
options={options}
status={status}
isValid={isValid}
help={help}
{...props}
/>
);
});
}

View File

@ -0,0 +1,23 @@
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useStatus } from 'stores/calculation/statuses/hooks';
import { useValue } from 'stores/calculation/values/hooks';
import type { Values } from 'stores/calculation/values/types';
import type { Elements } from '../config/map/values';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export default function buildReadonly<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value] = useValue(valueName);
const status = useStatus(elementName);
return <Component value={value} status={status} readOnly {...props} />;
});
}

View File

@ -1,28 +1,32 @@
import type { Elements } from '../config/map/values';
import { useStoreValue } from './hooks';
import { useStatus } from '@/stores/calculation/statuses/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useStatus } from 'stores/calculation/statuses/hooks';
import { useValidation } from 'stores/calculation/validation/hooks';
import type { Values } from 'stores/calculation/values/types';
import type { Elements } from '../config/map/values';
import { useStoreValue } from './hooks';
export type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildLink<T>(
export default function buildValue<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value] = useStoreValue(valueName);
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { isValid, help } = useValidation(elementName);
return (
<Component
href={status === 'Disabled' ? undefined : value || undefined}
disabled={!value || status === 'Disabled'}
loading={status === 'Loading'}
value={value}
setValue={setValue}
status={status}
isValid={isValid}
help={help}
{...props}
/>
);

View File

@ -1,18 +1,24 @@
import { useValue } from '@/stores/calculation/values/hooks';
/* eslint-disable import/prefer-default-export */
import { useEffect, useState } from 'react';
import { useValue } from 'stores/calculation/values/hooks';
import { useDebouncedCallback } from 'use-debounce';
export function useStoreValue(valueName) {
const [storeValue, setStoreValue] = useValue(valueName);
const [value, setValue] = useState(storeValue);
// eslint-disable-next-line object-curly-newline
const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, 350, { maxWait: 1000 });
useEffect(() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
}, [value]);
useEffect(
() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[value]
);
useEffect(() => {
setValue(storeValue);

View File

@ -0,0 +1,153 @@
import buildAction from '../builders/build-action';
import buildOptions from '../builders/build-options';
import buildReadonly from '../builders/build-readonly';
import buildValue from '../builders/build-value';
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
function wrapElementsBuilders<C, T extends Record<ValuesElements | ActionElements, C>>(arg: T) {
return arg;
}
const builders = wrapElementsBuilders({
cbxRecalcWithRevision: buildValue,
tbxLeaseObjectPrice: buildValue,
tbxLeaseObjectPriceWthtVAT: buildValue,
tbxVATInLeaseObjectPrice: buildValue,
tbxEngineHours: buildValue,
tbxSupplierDiscountRub: buildValue,
tbxSupplierDiscountPerc: buildValue,
tbxLeasingPeriod: buildValue,
tbxFirstPaymentPerc: buildValue,
tbxFirstPaymentRub: buildValue,
tbxLastPaymentPerc: buildValue,
tbxLastPaymentRub: buildValue,
selectImportProgram: buildOptions,
tbxImportProgramSum: buildReadonly,
tbxAddEquipmentPrice: buildValue,
tbxRedemptionPaymentSum: buildValue,
tbxParmentsDecreasePercent: buildValue,
tbxComissionPerc: buildValue,
tbxComissionRub: buildValue,
tbxSaleBonus: buildValue,
tbxIRR_Perc: buildValue,
tbxLeaseObjectCount: buildValue,
cbxWithTrailer: buildValue,
cbxLeaseObjectUsed: buildValue,
tbxMaxMass: buildValue,
tbxCountSeats: buildValue,
tbxMaxSpeed: buildValue,
tbxLeaseObjectYear: buildValue,
tbxLeaseObjectMotorPower: buildValue,
tbxEngineVolume: buildValue,
tbxDealerRewardSumm: buildValue,
tbxDealerBrokerRewardSumm: buildValue,
tbxIndAgentRewardSumm: buildValue,
tbxCalcDoubleAgentRewardSumm: buildValue,
tbxCalcBrokerRewardSum: buildValue,
tbxFinDepartmentRewardSumm: buildValue,
cbxInsDecentral: buildValue,
tbxInsFranchise: buildValue,
cbxInsUnlimitDrivers: buildValue,
tbxInsAgeDrivers: buildValue,
tbxInsExpDrivers: buildValue,
tbxINNForCalc: buildValue,
cbxLastPaymentRedemption: buildValue,
cbxPriceWithDiscount: buildValue,
cbxFullPriceWithDiscount: buildValue,
cbxCostIncrease: buildValue,
cbxInsurance: buildValue,
cbxRegistrationQuote: buildValue,
cbxTechnicalCardQuote: buildValue,
cbxNSIB: buildValue,
tbxQuoteName: buildValue,
cbxQuoteRedemptionGraph: buildValue,
cbxShowFinGAP: buildValue,
tbxCreditRate: buildValue,
tbxMaxPriceChange: buildValue,
tbxImporterRewardPerc: buildValue,
tbxImporterRewardRub: buildValue,
cbxDisableChecks: buildValue,
tbxMileage: buildValue,
tbxTotalPayments: buildValue,
tbxVehicleTaxInYear: buildValue,
tbxVehicleTaxInLeasingPeriod: buildValue,
tbxMinPriceChange: buildValue,
selectProduct: buildOptions,
selectClientRisk: buildOptions,
selectClientType: buildOptions,
selectSupplierCurrency: buildOptions,
selectSeasonType: buildOptions,
selectHighSeasonStart: buildOptions,
selectLeaseObjectType: buildOptions,
selectBrand: buildOptions,
selectModel: buildOptions,
selectConfiguration: buildOptions,
selectLeaseObjectUseFor: buildOptions,
selectLeaseObjectCategory: buildOptions,
selectEngineType: buildOptions,
selectDealer: buildOptions,
selectDealerPerson: buildOptions,
selectDealerRewardCondition: buildOptions,
selectDealerBroker: buildOptions,
selectDealerBrokerRewardCondition: buildOptions,
selectIndAgent: buildOptions,
selectIndAgentRewardCondition: buildOptions,
selectCalcDoubleAgent: buildOptions,
selectCalcDoubleAgentRewardCondition: buildOptions,
selectCalcBroker: buildOptions,
selectCalcBrokerRewardCondition: buildOptions,
selectCalcFinDepartment: buildOptions,
selectFinDepartmentRewardCondtion: buildOptions,
selectGPSBrand: buildOptions,
selectGPSModel: buildOptions,
selectRegionRegistration: buildOptions,
selectTownRegistration: buildOptions,
selectRegistration: buildOptions,
selectInsNSIB: buildOptions,
selectTracker: buildOptions,
selectTelematic: buildOptions,
selectTechnicalCard: buildOptions,
selectTarif: buildOptions,
selectRate: buildOptions,
selectLead: buildOptions,
selectOpportunity: buildOptions,
selectQuote: buildOptions,
selectObjectRegionRegistration: buildOptions,
selectObjectCategoryTax: buildOptions,
selectObjectTypeTax: buildOptions,
selectLegalClientRegion: buildOptions,
selectLegalClientTown: buildOptions,
selectSubsidy: buildOptions,
selectFuelCard: buildOptions,
radioBalanceHolder: buildOptions,
radioLastPaymentRule: buildOptions,
radioGraphType: buildOptions,
radioDeliveryTime: buildOptions,
radioInsKaskoType: buildOptions,
radioInfuranceOPF: buildOptions,
selectRequirementTelematic: buildOptions,
radioQuoteContactGender: buildOptions,
radioCalcType: buildOptions,
radioObjectRegistration: buildOptions,
radioTypePTS: buildOptions,
tbxBonusCoefficient: buildValue,
labelLeaseObjectRisk: buildReadonly,
tbxInsKaskoPriceLeasePeriod: buildReadonly,
labelIrrInfo: buildReadonly,
labelRegistrationDescription: buildReadonly,
labelDepreciationGroup: buildReadonly,
tbxSubsidySum: buildReadonly,
btnCreateKP: buildAction,
btnCalculate: buildAction,
linkDownloadKp: buildReadonly,
linkLeadUrl: buildReadonly,
linkOpportunityUrl: buildReadonly,
linkQuoteUrl: buildReadonly,
});
export default builders;

View File

@ -0,0 +1,159 @@
import type { ComponentProps } from 'react';
import * as elements from 'ui/elements';
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
function wrapComponentsMap<C, T extends Record<ValuesElements | ActionElements, C>>(arg: T) {
return arg;
}
const components = wrapComponentsMap({
selectProduct: elements.Select,
selectClientRisk: elements.Select,
selectClientType: elements.Select,
selectSupplierCurrency: elements.Select,
tbxLeaseObjectPrice: elements.InputNumber,
tbxLeaseObjectPriceWthtVAT: elements.InputNumber,
tbxVATInLeaseObjectPrice: elements.InputNumber,
tbxSupplierDiscountRub: elements.InputNumber,
tbxSupplierDiscountPerc: elements.InputNumber,
radioBalanceHolder: elements.Radio,
tbxSaleBonus: elements.InputNumber,
tbxFirstPaymentPerc: elements.InputNumber,
tbxFirstPaymentRub: elements.InputNumber,
radioLastPaymentRule: elements.Segmented,
tbxLastPaymentPerc: elements.InputNumber,
tbxLastPaymentRub: elements.InputNumber,
selectImportProgram: elements.Select,
tbxImportProgramSum: elements.InputNumber,
tbxAddEquipmentPrice: elements.InputNumber,
tbxRedemptionPaymentSum: elements.InputNumber,
tbxLeasingPeriod: elements.InputNumber,
radioGraphType: elements.Radio,
tbxParmentsDecreasePercent: elements.InputNumber,
selectSeasonType: elements.Select,
selectHighSeasonStart: elements.Select,
tbxComissionPerc: elements.InputNumber,
tbxComissionRub: elements.InputNumber,
selectLeaseObjectType: elements.Select,
selectBrand: elements.Select,
selectModel: elements.Select,
selectConfiguration: elements.Select,
cbxLeaseObjectUsed: elements.Checkbox,
radioDeliveryTime: elements.Segmented,
tbxLeaseObjectCount: elements.InputNumber,
selectLeaseObjectUseFor: elements.Select,
tbxLeaseObjectYear: elements.InputNumber,
selectLeaseObjectCategory: elements.Select,
selectEngineType: elements.Select,
tbxLeaseObjectMotorPower: elements.InputNumber,
tbxEngineVolume: elements.InputNumber,
tbxMaxMass: elements.InputNumber,
tbxCountSeats: elements.InputNumber,
tbxMaxSpeed: elements.InputNumber,
cbxWithTrailer: elements.Checkbox,
selectDealer: elements.Select,
selectDealerPerson: elements.Select,
selectDealerRewardCondition: elements.Select,
tbxDealerRewardSumm: elements.InputNumber,
selectDealerBroker: elements.Select,
selectDealerBrokerRewardCondition: elements.Select,
tbxDealerBrokerRewardSumm: elements.InputNumber,
selectIndAgent: elements.Select,
selectIndAgentRewardCondition: elements.Select,
tbxIndAgentRewardSumm: elements.InputNumber,
selectCalcDoubleAgent: elements.Select,
selectCalcDoubleAgentRewardCondition: elements.Select,
tbxCalcDoubleAgentRewardSumm: elements.InputNumber,
selectCalcBroker: elements.Select,
selectCalcBrokerRewardCondition: elements.Select,
tbxCalcBrokerRewardSum: elements.InputNumber,
selectCalcFinDepartment: elements.Select,
selectFinDepartmentRewardCondtion: elements.Select,
tbxFinDepartmentRewardSumm: elements.InputNumber,
cbxInsDecentral: elements.Switch,
radioInsKaskoType: elements.Radio,
tbxInsFranchise: elements.InputNumber,
cbxInsUnlimitDrivers: elements.Switch,
tbxInsAgeDrivers: elements.InputNumber,
tbxInsExpDrivers: elements.InputNumber,
tbxINNForCalc: elements.InputNumber,
selectGPSBrand: elements.Select,
selectGPSModel: elements.Select,
selectRegionRegistration: elements.Select,
selectTownRegistration: elements.Select,
radioInfuranceOPF: elements.Radio,
selectRegistration: elements.Select,
selectInsNSIB: elements.Select,
selectRequirementTelematic: elements.Select,
selectTracker: elements.Select,
selectTelematic: elements.Select,
selectTechnicalCard: elements.Select,
cbxLastPaymentRedemption: elements.Switch,
cbxPriceWithDiscount: elements.Switch,
cbxFullPriceWithDiscount: elements.Switch,
cbxCostIncrease: elements.Switch,
cbxInsurance: elements.Switch,
cbxRegistrationQuote: elements.Switch,
cbxTechnicalCardQuote: elements.Switch,
cbxNSIB: elements.Switch,
cbxQuoteRedemptionGraph: elements.Switch,
cbxShowFinGAP: elements.Switch,
tbxQuoteName: elements.Input,
radioQuoteContactGender: elements.Radio,
cbxDisableChecks: elements.Switch,
selectTarif: elements.Select,
tbxCreditRate: elements.InputNumber,
selectRate: elements.Select,
tbxMaxPriceChange: elements.InputNumber,
tbxImporterRewardPerc: elements.InputNumber,
tbxImporterRewardRub: elements.InputNumber,
selectLead: elements.Select,
selectOpportunity: elements.Select,
selectQuote: elements.Select,
cbxRecalcWithRevision: elements.Checkbox,
tbxIRR_Perc: elements.InputNumber,
tbxMileage: elements.InputNumber,
tbxEngineHours: elements.InputNumber,
radioCalcType: elements.Segmented,
tbxTotalPayments: elements.InputNumber,
radioObjectRegistration: elements.Radio,
selectObjectRegionRegistration: elements.Select,
tbxVehicleTaxInYear: elements.InputNumber,
tbxVehicleTaxInLeasingPeriod: elements.InputNumber,
selectObjectCategoryTax: elements.Select,
selectObjectTypeTax: elements.Select,
radioTypePTS: elements.Radio,
selectLegalClientRegion: elements.Select,
selectLegalClientTown: elements.Select,
selectSubsidy: elements.Select,
selectFuelCard: elements.Select,
tbxMinPriceChange: elements.InputNumber,
tbxBonusCoefficient: elements.InputNumber,
/** Readonly Elements */
labelLeaseObjectRisk: elements.Text,
tbxInsKaskoPriceLeasePeriod: elements.InputNumber,
labelIrrInfo: elements.Text,
labelRegistrationDescription: elements.Text,
labelDepreciationGroup: elements.Text,
tbxSubsidySum: elements.InputNumber,
/** Button Elements */
btnCreateKP: elements.Button,
btnCalculate: elements.Button,
/** Link Elements */
linkDownloadKp: elements.Link,
linkLeadUrl: elements.Link,
linkOpportunityUrl: elements.Link,
linkQuoteUrl: elements.Link,
});
export default components;
type ComponentsTypes = typeof components;
export type ElementsProps = {
[Component in keyof ComponentsTypes]: ComponentProps<ComponentsTypes[Component]>;
};

View File

@ -1,13 +1,9 @@
/* eslint-disable canonical/sort-keys */
import { MAX_FRANCHISE, MAX_LEASING_PERIOD } from 'constants/values';
import dayjs from 'dayjs';
import { formatter, formatterExtra, parser } from 'tools/number';
import DownloadOutlined from 'ui/elements/icons/DownloadOutlined';
import CurrencyAddon from '../addons/currency-addon';
import type { ElementsProps } from './elements-components';
import { MAX_FRANCHISE, MAX_LEASING_PERIOD } from '@/constants/values';
import dayjs from 'dayjs';
import { parser } from 'tools/number';
import { DownloadOutlined, PlusOutlined } from 'ui/elements/icons';
import { createFormatter } from 'ui/elements/InputNumber';
const formatter = createFormatter({ minimumFractionDigits: 2, maximumFractionDigits: 2 });
const props: Partial<ElementsProps> = {
tbxLeaseObjectPrice: {
@ -18,9 +14,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxLeaseObjectPriceWthtVAT: {
min: 0,
@ -30,9 +23,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxVATInLeaseObjectPrice: {
min: 0,
@ -42,18 +32,12 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxEngineHours: {
min: 0,
step: 10,
precision: 2,
addonAfter: 'ч.',
style: {
width: '100%',
},
},
tbxSupplierDiscountRub: {
min: 0,
@ -62,10 +46,7 @@ const props: Partial<ElementsProps> = {
precision: 2,
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
addonAfter: '₽',
},
tbxSupplierDiscountPerc: {
min: 0,
@ -73,9 +54,6 @@ const props: Partial<ElementsProps> = {
precision: 2,
addonAfter: '%',
style: {
width: '100%',
},
},
radioBalanceHolder: {
optionType: 'button',
@ -88,9 +66,6 @@ const props: Partial<ElementsProps> = {
precision: 2,
addonAfter: '%',
style: {
width: '100%',
},
},
radioLastPaymentRule: {
block: true,
@ -100,11 +75,8 @@ const props: Partial<ElementsProps> = {
max: 50,
precision: 4,
parser,
formatter: createFormatter({ minimumFractionDigits: 4, maximumFractionDigits: 4 }),
formatter: formatterExtra,
addonAfter: '%',
style: {
width: '100%',
},
},
tbxFirstPaymentRub: {
min: 0,
@ -114,9 +86,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxLastPaymentPerc: {
min: 0,
@ -124,11 +93,8 @@ const props: Partial<ElementsProps> = {
step: 1,
precision: 6,
parser,
formatter: createFormatter({ minimumFractionDigits: 6, maximumFractionDigits: 6 }),
formatter: formatterExtra,
addonAfter: '%',
style: {
width: '100%',
},
},
tbxLastPaymentRub: {
min: 0,
@ -138,9 +104,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxRedemptionPaymentSum: {
min: 1000,
@ -150,55 +113,34 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxLeasingPeriod: {
min: 13,
max: MAX_LEASING_PERIOD,
addonAfter: 'мес.',
style: {
width: '100%',
},
},
tbxSubsidySum: {
min: 0,
precision: 2,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxImportProgramSum: {
min: 0,
precision: 2,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxAddEquipmentPrice: {
min: 0,
precision: 2,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxParmentsDecreasePercent: {
min: 50,
max: 99,
style: {
width: '100%',
},
},
tbxComissionPerc: {
min: 0,
max: 100,
style: {
width: '100%',
},
},
tbxComissionRub: {
min: 0,
@ -207,9 +149,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
selectLeaseObjectType: {
showSearch: true,
@ -231,9 +170,6 @@ const props: Partial<ElementsProps> = {
min: 1,
max: 1000,
addonAfter: 'шт.',
style: {
width: '100%',
},
},
selectLeaseObjectUseFor: {
showSearch: true,
@ -242,9 +178,6 @@ const props: Partial<ElementsProps> = {
tbxLeaseObjectYear: {
min: 1994,
max: dayjs().year(),
style: {
width: '100%',
},
},
selectLeaseObjectCategory: {
showSearch: false,
@ -261,9 +194,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'л.с.',
style: {
width: '100%',
},
},
tbxEngineVolume: {
min: 0,
@ -271,11 +201,8 @@ const props: Partial<ElementsProps> = {
step: 0.5,
precision: 4,
parser,
formatter: createFormatter({ minimumFractionDigits: 4, maximumFractionDigits: 4 }),
formatter: formatterExtra,
addonAfter: 'л',
style: {
width: '100%',
},
},
tbxMaxMass: {
min: 0,
@ -284,18 +211,12 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'кг',
style: {
width: '100%',
},
},
tbxCountSeats: {
min: 0,
max: 2000,
precision: 0,
parser,
style: {
width: '100%',
},
formatter,
},
tbxMaxSpeed: {
min: 0,
@ -303,9 +224,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'км/ч',
style: {
width: '100%',
},
},
selectDealer: {
showSearch: true,
@ -316,54 +234,40 @@ const props: Partial<ElementsProps> = {
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxDealerBrokerRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxIndAgentRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxCalcDoubleAgentRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxCalcBrokerRewardSum: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxFinDepartmentRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
radioInsKaskoType: {
optionType: 'button',
buttonStyle: 'solid',
},
tbxInsFranchise: {
min: 0,
@ -373,23 +277,14 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxInsAgeDrivers: {
// min: 18,
// max: 99,
style: {
width: '100%',
},
},
tbxInsExpDrivers: {
// min: 0,
// max: 99,
style: {
width: '100%',
},
},
selectRegionRegistration: {
showSearch: true,
@ -403,58 +298,34 @@ const props: Partial<ElementsProps> = {
optionType: 'button',
buttonStyle: 'solid',
},
btnCalculate: {
children: 'Рассчитать график',
type: 'primary',
block: true,
},
btnCreateKP: {
type: 'primary',
children: 'Создать КП',
icon: <PlusOutlined rev="" />,
},
btnCreateKPMini: {
children: '',
type: 'primary',
icon: <PlusOutlined rev="" />,
block: true,
text: 'Создать КП',
},
tbxCreditRate: {
min: 0,
max: 99.99,
step: 0.1,
style: {
width: '100%',
},
},
tbxMaxPriceChange: {
min: 0,
max: 1_000_000_000,
max: 34_999_990,
step: 10_000,
parser,
formatter,
style: {
width: '100%',
},
},
tbxMinPriceChange: {
min: 0,
max: 1_000_000_000,
max: 34_999_990,
step: 10_000,
parser,
formatter,
style: {
width: '100%',
},
},
tbxImporterRewardPerc: {
min: 0,
max: 99.99,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxImporterRewardRub: {
min: 0,
@ -463,9 +334,6 @@ const props: Partial<ElementsProps> = {
precision: 2,
parser,
formatter,
style: {
width: '100%',
},
},
selectLead: {
showSearch: true,
@ -479,42 +347,35 @@ const props: Partial<ElementsProps> = {
showSearch: true,
optionFilterProp: 'label',
},
btnCalculate: {
text: 'Рассчитать график',
type: 'primary',
},
tbxIRR_Perc: {
min: -500,
min: 0,
max: 500,
step: 0.0001,
precision: 6,
parser,
formatter: createFormatter({ minimumFractionDigits: 6, maximumFractionDigits: 6 }),
formatter: formatterExtra,
addonAfter: '%',
style: {
width: '100%',
},
},
tbxPi: {
min: -500,
max: 500,
step: 0.0001,
precision: 6,
parser,
formatter: createFormatter({ minimumFractionDigits: 6, maximumFractionDigits: 6 }),
addonAfter: '%',
style: {
width: '100%',
},
linkDownloadKp: {
type: 'primary',
text: 'Скачать КП',
icon: DownloadOutlined,
},
linkDownloadKp: { children: 'Скачать КП', icon: <DownloadOutlined rev="" />, type: 'primary' },
tbxMileage: {
min: 0,
step: 100,
precision: 2,
addonAfter: 'км',
style: {
width: '100%',
},
},
cbxRecalcWithRevision: {
children: 'Пересчет без пересмотра',
text: 'Пересчет без пересмотра',
style: {
marginBottom: '8px',
},
},
tbxTotalPayments: {
min: 0,
@ -523,9 +384,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxVehicleTaxInYear: {
min: 0,
@ -535,9 +393,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxVehicleTaxInLeasingPeriod: {
min: 0,
@ -547,9 +402,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
selectObjectRegionRegistration: {
showSearch: true,
@ -562,9 +414,6 @@ const props: Partial<ElementsProps> = {
formatter,
readOnly: true,
controls: false,
style: {
width: '100%',
},
},
selectLegalClientRegion: {
showSearch: true,
@ -574,6 +423,10 @@ const props: Partial<ElementsProps> = {
showSearch: true,
optionFilterProp: 'label',
},
radioInfuranceOPF: {
optionType: 'button',
buttonStyle: 'solid',
},
radioGraphType: {
spaceProps: {
direction: 'vertical',
@ -589,35 +442,6 @@ const props: Partial<ElementsProps> = {
direction: 'vertical',
},
},
selectUser: {
showSearch: true,
optionFilterProp: 'label',
},
tbxBonusCoefficient: {
min: 0,
max: 10,
step: 0.1,
precision: 4,
formatter: createFormatter({ minimumFractionDigits: 4, maximumFractionDigits: 4 }),
style: {
width: '100%',
},
},
tbxVIN: {
onInput: (e) => {
e.currentTarget.value = e.currentTarget.value.toUpperCase();
},
},
labelIrrInfo: {
style: {
marginBottom: '18px',
},
},
labelDepreciationGroup: {
style: {
marginBottom: '18px',
},
},
};
export default props;

View File

@ -1,3 +1,4 @@
/* eslint-disable object-curly-newline */
import type map from '../map';
import overrideRender from './override';
import render from './render';

View File

@ -0,0 +1,205 @@
/* eslint-disable object-curly-newline */
import { Container, Head } from 'Components/Layout/Element';
import { observer } from 'mobx-react-lite';
import type { ComponentProps } from 'react';
import { useStore } from 'stores/hooks';
import Link from 'ui/elements/Link';
import Tooltip from 'ui/elements/Tooltip';
import buildReadonly from '../../builders/build-readonly';
import builders from '../elements-builders';
import components from '../elements-components';
import elementsProps from '../elements-props';
import titles from '../elements-titles';
import map from '../map';
import type { RenderProps } from './types';
const defaultLinkProps: ComponentProps<typeof Link> = {
text: 'Открыть в CRM',
type: 'link',
};
const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
selectLead: {
render: () => {
const elementName = 'selectLead';
const title = titles.selectLead;
const valueName = map.selectLead;
const Component = components.selectLead;
const props = elementsProps.selectLead;
const builder = builders.selectLead;
const Element = builder(Component, {
elementName,
valueName,
});
const LinkComponent = buildReadonly(Link, {
elementName: 'linkLeadUrl',
valueName: 'leadUrl',
});
return (
<Container key={elementName}>
<Head
htmlFor={elementName}
title={title}
addon={<LinkComponent {...defaultLinkProps} />}
/>
<Element {...props} id={elementName} />
</Container>
);
},
},
selectOpportunity: {
render: () => {
const elementName = 'selectOpportunity';
const title = titles.selectOpportunity;
const valueName = map.selectOpportunity;
const Component = components.selectOpportunity;
const props = elementsProps.selectOpportunity;
const builder = builders.selectOpportunity;
const Element = builder(Component, {
elementName,
valueName,
});
const LinkComponent = buildReadonly(Link, {
elementName: 'linkOpportunityUrl',
valueName: 'opportunityUrl',
});
return (
<Container key={elementName}>
<Head
htmlFor={elementName}
title={title}
addon={<LinkComponent {...defaultLinkProps} />}
/>
<Element {...props} id={elementName} />
</Container>
);
},
},
selectQuote: {
render: () => {
const elementName = 'selectQuote';
const title = titles.selectQuote;
const valueName = map.selectQuote;
const Component = components.selectQuote;
const props = elementsProps.selectQuote;
const builder = builders.selectQuote;
const Element = builder(Component, {
elementName,
valueName,
});
const LinkComponent = buildReadonly(Link, {
elementName: 'linkQuoteUrl',
valueName: 'quoteUrl',
});
return (
<Container key={elementName}>
<Head
htmlFor={elementName}
title={title}
addon={<LinkComponent {...defaultLinkProps} />}
/>
<Element {...props} id={elementName} />
</Container>
);
},
},
tbxVehicleTaxInYear: {
render: () => {
const elementName = 'tbxVehicleTaxInYear';
const title = titles.tbxVehicleTaxInYear;
const valueName = map.tbxVehicleTaxInYear;
const Component = components.tbxVehicleTaxInYear;
const props = elementsProps.tbxVehicleTaxInYear;
const builder = builders.tbxVehicleTaxInYear;
const Element = builder(Component, {
elementName,
valueName,
});
return (
<Tooltip title="Без учета налога на роскошь" placement="topLeft">
<Container>
<Head htmlFor={elementName} title={title} />
<Element {...props} id={elementName} />
</Container>
</Tooltip>
);
},
},
selectHighSeasonStart: {
render: () => {
const elementName = 'selectHighSeasonStart';
const title = titles.selectHighSeasonStart;
const valueName = map.selectHighSeasonStart;
const Component = components.selectHighSeasonStart;
const props = elementsProps.selectHighSeasonStart;
const builder = builders.selectHighSeasonStart;
const Element = builder(Component, {
elementName,
valueName,
});
return (
<Tooltip title="С какого платежа начинается полный высокий сезон" placement="topLeft">
<Container>
<Head htmlFor={elementName} title={title} />
<Element {...props} id={elementName} />
</Container>
</Tooltip>
);
},
},
selectSeasonType: {
render: () => {
const elementName = 'selectSeasonType';
const valueName = map.selectSeasonType;
const Component = components.selectSeasonType;
const props = elementsProps.selectSeasonType;
const builder = builders.selectSeasonType;
const Element = builder(Component, {
elementName,
valueName,
});
const Title = observer(() => {
const { $calculation } = useStore();
const graphType = $calculation.element('radioGraphType').getValue();
switch (graphType) {
case 100_000_001:
return <span>Тип дегрессии</span>;
case 100_000_003:
return <span>Тип сезонности</span>;
default:
return <span>{titles.selectSeasonType}</span>;
}
});
return (
<Container>
<Head htmlFor={elementName} title={<Title />} />
<Element {...props} id={elementName} />
</Container>
);
},
},
};
export default overrideRender;

View File

@ -1,16 +1,17 @@
/* eslint-disable object-curly-newline */
import { Container, Head } from 'Components/Layout/Element';
import builders from '../elements-builders';
import components from '../elements-components';
import elementsProps from '../elements-props';
import titles from '../elements-titles';
import types from '../elements-types';
import map from '../map';
import { Container, Head } from '@/Components/Layout/Element';
const render = Object.keys(map).reduce((acc, elementName) => {
const title = titles[elementName];
const valueName = map[elementName];
const Component = components[elementName];
const props = elementsProps[elementName];
const { builder } = types[elementName]();
const builder = builders[elementName];
const Element = builder(Component, {
elementName,

View File

@ -1,8 +1,7 @@
/* eslint-disable canonical/sort-keys */
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
const titles: Record<ActionElements | ValuesElements, string> = {
const titles: Record<ValuesElements | ActionElements, string> = {
selectLead: 'Интерес',
selectOpportunity: 'Лизинговая сделка',
selectQuote: 'Предложение',
@ -73,6 +72,8 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectGPSModel: 'Модель GPS',
selectRegionRegistration: 'Регион регистрации',
selectTownRegistration: 'Город регистрации',
radioInfuranceOPF: 'ОПФ для расчета страховки',
radioInsKaskoType: 'Тип страхования КАСКО',
cbxInsDecentral: 'Децентрализованное страхование',
tbxInsFranchise: 'Франшиза',
cbxInsUnlimitDrivers: 'Неограниченное число водителей',
@ -93,7 +94,7 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectTarif: 'Тариф',
tbxCreditRate: 'Ставка привлечения, %',
selectRate: 'Ставка привлечения',
tbxMaxPriceChange: 'Макс. возможное изменение стоимости ПЛ',
tbxMaxPriceChange: 'Макс.возможное изменение стоимости ПЛ',
tbxImporterRewardPerc: 'АВ импортера, %',
tbxImporterRewardRub: 'АВ импортера, руб.',
cbxDisableChecks: 'Отключить все проверки',
@ -113,6 +114,7 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectObjectCategoryTax: 'Кат. по ТР ТС 018/2011',
selectObjectTypeTax: 'Тип ТС для ТН',
radioTypePTS: 'Тип ПТС',
tbxINNForCalc: 'ИНН контрагента',
selectLegalClientRegion: 'Регион по юр.адресу клиента',
selectLegalClientTown: 'Город по юр.адресу клиента',
selectSubsidy: 'Субсидия',
@ -122,15 +124,6 @@ const titles: Record<ActionElements | ValuesElements, string> = {
tbxLeaseObjectPriceWthtVAT: 'Стоимость ПЛ без НДС',
tbxVATInLeaseObjectPrice: 'НДС в стоимости ПЛ',
tbxBonusCoefficient: 'Коэффициент снижения бонуса',
selectLeasingWithoutKasko: 'Лизинг без КАСКО',
tbxVIN: 'VIN',
selectUser: 'Пользователь',
cbxSupplierFinancing: 'Финансирование поставщика',
tbxPi: 'PI',
cbxPartialVAT: 'Частичный НДС',
cbxFloatingRate: 'Плавающая ставка',
cbxQuotePriceWithFullVAT: 'Отображать Стоимость ПЛ с полным НДС',
cbxQuoteShowAcceptLimit: 'Отображать одобренный лимит',
/** Link Elements */
linkDownloadKp: '',
@ -149,7 +142,6 @@ const titles: Record<ActionElements | ValuesElements, string> = {
/** Action Elements */
btnCalculate: '',
btnCreateKP: '',
btnCreateKPMini: '',
};
export default titles;

View File

@ -5,7 +5,6 @@ function wrapElementsMap<T extends Record<string, string>>(arg: T) {
const elementsToActions = wrapElementsMap({
btnCalculate: 'calculate',
btnCreateKP: 'create-kp',
btnCreateKPMini: 'create-kp',
});
export default elementsToActions;

View File

@ -1,5 +1,4 @@
/* eslint-disable canonical/sort-keys */
import type { CalculationValues, Values } from '@/stores/calculation/values/types';
import type { CalculationValues, Values } from 'stores/calculation/values/types';
function wrapElementsMap<T extends Record<string, Values>>(arg: T) {
return arg;
@ -79,11 +78,14 @@ const elementsToValues = wrapElementsMap({
selectGPSModel: 'GPSModel',
selectRegionRegistration: 'regionRegistration',
selectTownRegistration: 'townRegistration',
radioInfuranceOPF: 'infuranceOPF',
radioInsKaskoType: 'insKaskoType',
cbxInsDecentral: 'insDecentral',
tbxInsFranchise: 'insFranchise',
cbxInsUnlimitDrivers: 'insUnlimitDrivers',
tbxInsAgeDrivers: 'insAgeDrivers',
tbxInsExpDrivers: 'insExpDrivers',
tbxINNForCalc: 'INNForCalc',
cbxLastPaymentRedemption: 'lastPaymentRedemption',
cbxPriceWithDiscount: 'priceWithDiscount',
cbxFullPriceWithDiscount: 'fullPriceWithDiscount',
@ -126,14 +128,6 @@ const elementsToValues = wrapElementsMap({
selectFuelCard: 'fuelCard',
tbxMinPriceChange: 'minPriceChange',
tbxBonusCoefficient: 'bonusCoefficient',
tbxVIN: 'vin',
selectUser: 'user',
cbxSupplierFinancing: 'supplierFinancing',
tbxPi: 'pi',
cbxPartialVAT: 'partialVAT',
cbxFloatingRate: 'floatingRate',
cbxQuotePriceWithFullVAT: 'quotePriceWithFullVAT',
cbxQuoteShowAcceptLimit: 'quoteShowAcceptLimit',
/** Readonly Elements */
labelLeaseObjectRisk: 'leaseObjectRiskName',
@ -141,10 +135,9 @@ const elementsToValues = wrapElementsMap({
labelIrrInfo: 'irrInfo',
labelRegistrationDescription: 'registrationDescription',
labelDepreciationGroup: 'depreciationGroup',
selectLeasingWithoutKasko: 'leasingWithoutKasko',
/** Link Elements */
linkDownloadKp: 'downloadKp',
linkDownloadKp: 'kpUrl',
linkLeadUrl: 'leadUrl',
linkOpportunityUrl: 'opportunityUrl',
linkQuoteUrl: 'quoteUrl',
@ -163,9 +156,3 @@ export type Elements = keyof ElementsTypes;
export function getValueName(elementName: Elements) {
return elementsToValues[elementName];
}
export function getElementName(valueName: Values) {
return (Object.keys(elementsToValues) as Elements[]).find(
(elementName) => elementsToValues[elementName] === valueName
);
}

View File

@ -0,0 +1,2 @@
export { default as Form } from './Form';
export { default as Settings } from './Settings';

Some files were not shown because too many files have changed in this diff Show More