Compare commits

..

21 Commits

Author SHA1 Message Date
vchikalkin
357bb1e3a3 use ssg 2023-03-12 11:37:12 +03:00
vchikalkin
2defb4f79b for 3b55956 2023-03-10 16:31:04 +03:00
vchikalkin
f86744de70 fix radioCalcType 2023-03-10 16:03:15 +03:00
vchikalkin
3b559561d7 fix selectRate 2023-03-10 15:51:29 +03:00
vchikalkin
6c5edfefae fix selectTarif 2023-03-10 15:04:22 +03:00
vchikalkin
7a91f870fa apollo: fix caching 2023-03-10 14:39:31 +03:00
vchikalkin
5a73a9c8a3 remove field radioInsKaskoType 2023-03-10 13:53:41 +03:00
vchikalkin
0d2682cc27 перенесены все реакции insurance 2023-03-10 13:41:42 +03:00
vchikalkin
75aa3217b3 process/insurance: validation 2023-03-10 11:17:40 +03:00
vchikalkin
7fe8de83c8 merge experimental/zod-validation 2023-03-10 09:37:37 +03:00
vchikalkin
5748bb9ffd fix build 2023-03-02 16:57:10 +03:00
vchikalkin
9ae996b1ba process/insurance: insurance-table pt.1 2023-03-02 16:52:34 +03:00
vchikalkin
fc3610079d process/configurator: auto set value for specific fields 2023-03-02 15:26:03 +03:00
vchikalkin
86c9bdb113 process: add insurance reactions 2023-03-02 15:16:10 +03:00
vchikalkin
f93426767c use dayjs().utc(false).format('YYYY-MM-DD') 2023-03-02 13:45:04 +03:00
vchikalkin
581410138c move apollo config to /config/apollo.js 2023-03-02 13:08:00 +03:00
vchikalkin
f3c4c33a54 apollo: replace InMemoryCache modify settings to ApolloLink 2023-03-02 12:46:11 +03:00
vchikalkin
0ccc0e376c fix load-kp 2023-03-01 19:10:27 +03:00
vchikalkin
52784fb910 process: add-product 2023-03-01 18:44:57 +03:00
vchikalkin
b36f9772ec apollo: modify evo_equipment.evo_name 2023-03-01 16:23:02 +03:00
vchikalkin
7b401c2365 apollo: modify evo_addproduct_type.evo_name 2023-03-01 16:14:19 +03:00
425 changed files with 18449 additions and 44604 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
.git
README.md
node_modules

12
.env Normal file
View File

@ -0,0 +1,12 @@
####### COMMON #######
USE_DEV_COLORS=
BASE_PATH=
####### USERS ########
USERS_SUPER=["akalinina","vchikalkin"]
####### URLS ########
URL_GET_USER_DIRECT=
URL_CRM_GRAPHQL_DIRECT=
URL_CORE_FINGAP_DIRECT=
URL_1C_TRANSTAX_DIRECT=

View File

@ -8,5 +8,6 @@ coverage
.eslintrc.js
**/*.config.js
**/scripts
packages/eslint-config-custom/*
**/package.json
turbo.json

View File

@ -1,8 +1,10 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ['custom', 'custom/rules'],
settings: {
next: {
rootDir: ['apps/*/'],
rootDir: ['packages/web/'],
},
react: {
version: 'detect',

2
.gitignore vendored
View File

@ -68,5 +68,3 @@ yarn-error.log*
.turbo
# End of https://www.toptal.com/developers/gitignore/api/turbo
.pnpm

View File

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

View File

@ -13,9 +13,9 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.removeUnusedImports": "explicit"
"source.fixAll": true,
"source.fixAll.eslint": true,
"source.removeUnusedImports": true
},
"workbench.editor.labelFormat": "short",
"eslint.workingDirectories": [{ "directory": "apps/web", "changeProcessCWD": true }],

104
README.md
View File

@ -1,28 +1,59 @@
# Turborepo starter
# Turborepo Docker starter
This is an official starter Turborepo.
This is an official Docker starter Turborepo.
## What's inside?
This turborepo uses [Yarn](https://classic.yarnpkg.com/lang/en/) as a package manager. It includes the following packages/apps:
### Apps and Packages
- `web`: a [Next.js](https://nextjs.org/) app
- `api`: an [Express](https://expressjs.com/) server
- `ui`: ui: a React component library
- `eslint-config-custom`: `eslint` configurations for client side applications (includes `eslint-config-next` and `eslint-config-prettier`)
- `eslint-config-custom-server`: `eslint` configurations for server side applications (includes `eslint-config-next` and `eslint-config-prettier`)
- `scripts`: Jest configurations
- `logger`: Isomorphic logger (a small wrapper around console.log)
- `tsconfig`: tsconfig.json;s used throughout the monorepo
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
## Using this example
Run the following command:
```sh
npx create-turbo@latest
npx degit vercel/turbo/examples/with-docker with-docker
cd with-docker
yarn install
git init . && git add . && git commit -m "Init"
```
## What's inside?
### Docker
This Turborepo includes the following packages/apps:
This repo is configured to be built with Docker, and Docker compose. To build all apps in this repo:
### Apps and Packages
```
# Create a network, which allows containers to communicate
# with each other, by using their container name as a hostname
docker network create app_network
- `docs`: a [Next.js](https://nextjs.org/) app
- `web`: another [Next.js](https://nextjs.org/) app
- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
- `@repo/tsconfig`: `tsconfig.json`s used throughout the monorepo
# Build prod using new BuildKit engine
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml build --parallel
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
# Start prod in detached mode
docker-compose -f docker-compose.yml up -d
```
Open http://localhost:3000.
To shutdown all running containers:
```
# Stop all running containers
docker kill $(docker ps -q) && docker rm $(docker ps -a -q)
```
### Utilities
@ -30,52 +61,5 @@ This Turborepo has some additional tools already setup for you:
- [TypeScript](https://www.typescriptlang.org/) for static type checking
- [ESLint](https://eslint.org/) for code linting
- [Jest](https://jestjs.io) test runner for all things JavaScript
- [Prettier](https://prettier.io) for code formatting
### Build
To build all apps and packages, run the following command:
```
cd my-turborepo
pnpm build
```
### Develop
To develop all apps and packages, run the following command:
```
cd my-turborepo
pnpm dev
```
### Remote Caching
Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup), then enter the following commands:
```
cd my-turborepo
npx turbo login
```
This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
```
npx turbo link
```
## Useful Links
Learn more about the power of Turborepo:
- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)

View File

@ -1,7 +0,0 @@
.git
Dockerfile
.dockerignore
node_modules
*.log
dist
README.md

View File

@ -1,13 +0,0 @@
const { createConfig } = require('@vchikalkin/eslint-config-awesome');
module.exports = createConfig('typescript', {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
ignorePatterns: ['*.config.js', '.eslintrc.js'],
rules: {
'import/no-duplicates': 'off',
'import/consistent-type-specifier-style': 'off',
},
});

56
apps/api/.gitignore vendored
View File

@ -1,56 +0,0 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

View File

@ -1,4 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@ -1,45 +0,0 @@
# This Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker.
# Make sure you update both files!
FROM node:alpine AS builder
RUN corepack enable && corepack prepare pnpm@8.9.0 --activate
ENV PNPM_HOME=/usr/local/bin
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app
RUN pnpm add -g turbo@1.12.4 dotenv-cli
COPY . .
RUN turbo prune --scope=api --docker
# Add lockfile and package.json's of isolated subworkspace
FROM node:alpine AS installer
RUN corepack enable && corepack prepare pnpm@latest --activate
ENV PNPM_HOME=/usr/local/bin
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
# First install dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
RUN pnpm install
# Build the project and its dependencies
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
RUN pnpm dotenv -e .env turbo run build --filter=api...
FROM node:alpine AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nestjs
RUN adduser --system --uid 1001 nestjs
USER nestjs
COPY --from=installer /app .
CMD node apps/api/dist/main.js

View File

@ -1,73 +0,0 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ pnpm install
```
## Running the app
```bash
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod
```
## Test
```bash
# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

View File

@ -1,74 +0,0 @@
{
"name": "api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/cache-manager": "^2.2.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-fastify": "^10.3.3",
"cache-manager": "^5.4.0",
"cache-manager-ioredis": "^2.1.0",
"ioredis": "^5.3.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@vchikalkin/eslint-config-awesome": "^1.1.6",
"eslint": "^8.52.0",
"fastify": "^4.26.1",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"shared": "workspace:*",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "29.1.1",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -1,16 +0,0 @@
import { ProxyModule } from './proxy/proxy.module';
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
ProxyModule,
],
providers: [],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AppModule {}

View File

@ -1,3 +0,0 @@
import { seconds } from 'src/utils/time';
export const DEFAULT_CACHE_TTL = seconds().fromMinutes(15);

View File

@ -1,3 +0,0 @@
import envSchema from './schema/env';
export const env = envSchema.parse(process.env);

View File

@ -1,21 +0,0 @@
import { DEFAULT_CACHE_TTL } from '../constants';
import { z } from 'zod';
const envSchema = z.object({
CACHE_TTL: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default(DEFAULT_CACHE_TTL.toString()),
PORT: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default('3001'),
REDIS_HOST: z.string(),
REDIS_PORT: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default('6379'),
URL_CRM_GRAPHQL_DIRECT: z.string(),
});
export default envSchema;

View File

@ -1,15 +0,0 @@
import { AppModule } from './app.module';
import { env } from './config/env';
import { NestFactory } from '@nestjs/core';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import { FastifyAdapter } from '@nestjs/platform-fastify';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen(env.PORT, '0.0.0.0');
}
bootstrap();

View File

@ -1,64 +0,0 @@
import { seconds } from 'src/utils/time';
export const queryTTL: Record<string, number | false> = {
GetAddProductType: seconds().fromHours(12),
GetAddproductTypes: seconds().fromHours(12),
GetAgent: seconds().fromHours(12),
GetBrand: seconds().fromHours(3),
GetBrands: seconds().fromHours(3),
GetCoefficients: seconds().fromHours(12),
GetConfiguration: seconds().fromHours(3),
GetConfigurations: seconds().fromMinutes(15),
GetCurrencyChanges: seconds().fromHours(1),
GetDealer: seconds().fromHours(1),
GetDealerPerson: seconds().fromHours(1),
GetDealerPersons: seconds().fromHours(1),
GetDealers: seconds().fromMinutes(15),
GetEltInsuranceRules: seconds().fromHours(12),
GetFuelCards: seconds().fromHours(12),
GetGPSBrands: seconds().fromHours(24),
GetGPSModels: seconds().fromHours(24),
GetImportProgram: seconds().fromHours(12),
GetInsNSIBTypes: seconds().fromHours(12),
GetInsuranceCompanies: seconds().fromHours(12),
GetInsuranceCompany: seconds().fromHours(12),
GetLead: false,
GetLeadUrl: seconds().fromHours(12),
GetLeads: false,
GetLeaseObjectType: seconds().fromHours(24),
GetLeaseObjectTypes: seconds().fromHours(24),
GetLeasingWithoutKaskoTypes: seconds().fromHours(12),
GetModel: seconds().fromHours(3),
GetModels: seconds().fromMinutes(15),
GetOpportunities: false,
GetOpportunity: false,
GetOpportunityUrl: seconds().fromHours(12),
GetOsagoAddproductTypes: seconds().fromHours(12),
GetProduct: seconds().fromHours(12),
GetProducts: seconds().fromHours(12),
GetQuote: false,
GetQuoteData: false,
GetQuoteUrl: seconds().fromHours(12),
GetQuotes: false,
GetRate: seconds().fromHours(12),
GetRates: seconds().fromHours(12),
GetRegion: seconds().fromHours(24),
GetRegions: seconds().fromHours(24),
GetRegistrationTypes: seconds().fromHours(12),
GetRewardCondition: seconds().fromHours(1),
GetRewardConditions: seconds().fromHours(1),
GetRoles: seconds().fromHours(12),
GetSotCoefficientType: seconds().fromHours(12),
GetSubsidies: seconds().fromHours(12),
GetSubsidy: seconds().fromHours(12),
GetSystemUser: seconds().fromHours(12),
GetTarif: seconds().fromHours(12),
GetTarifs: seconds().fromHours(12),
GetTechnicalCards: seconds().fromHours(12),
GetTelematicTypes: seconds().fromHours(12),
GetTown: seconds().fromHours(24),
GetTowns: seconds().fromHours(24),
GetTrackerTypes: seconds().fromHours(12),
GetTransactionCurrencies: seconds().fromHours(12),
GetTransactionCurrency: seconds().fromHours(12),
};

View File

@ -1,127 +0,0 @@
import { queryTTL } from './lib/config';
import type { GQLRequest, GQLResponse } from './types';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import {
All,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Inject,
Query,
Req,
Res,
} from '@nestjs/common';
import type { Cache } from 'cache-manager';
import { FastifyReply, FastifyRequest } from 'fastify';
import type { QueryItem } from 'shared/types/cache';
import { env } from 'src/config/env';
type RedisStore = Omit<Cache, 'set'> & {
set: (key: string, value: unknown, { ttl }: { ttl: number }) => Promise<void>;
};
@Controller('proxy')
export class ProxyController {
constructor(
@Inject(CACHE_MANAGER) private readonly cacheManager: RedisStore,
) {}
@All('/graphql')
public async graphql(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
const { operationName, query, variables } = req.body as GQLRequest;
const key = `${operationName} ${JSON.stringify(variables)}`;
const cached = await this.cacheManager.get(key);
if (cached) return reply.send(cached);
const response = await fetch(env.URL_CRM_GRAPHQL_DIRECT, {
body: JSON.stringify({ operationName, query, variables }),
headers: {
Authorization: req.headers.authorization,
'Content-Type': 'application/json',
Cookie: req.headers.cookie,
},
method: req.method,
});
const data = (await response.json()) as GQLResponse;
if (!response.ok || data?.error || data?.errors?.length)
throw new HttpException(
response.statusText,
response.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
const ttl = queryTTL[operationName];
if (data && ttl !== false)
await this.cacheManager.set(key, data, { ttl: ttl || env.CACHE_TTL });
return reply.send(data);
}
@Get('/get-queries')
public async getQueriesList(@Res() reply: FastifyReply) {
const res = await this.getAllQueries();
return reply.send(res);
}
private async getAllQueries() {
const list = await this.cacheManager.store.keys('*');
return (Object.keys(queryTTL) as Array<keyof typeof queryTTL>).reduce(
(acc, queryName) => {
const queries = list.filter((x) => x.split(' ').at(0) === queryName);
if (queries.length) {
const ttl = queryTTL[queryName];
acc[queryName] = { queries, ttl };
}
return acc;
},
{} as Record<string, QueryItem>,
);
}
@Delete('/delete-query')
public async deleteQuery(
@Query('queryKey') queryKey: string,
@Res() reply: FastifyReply,
) {
try {
await this.cacheManager.del(queryKey);
return reply.send('ok');
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Delete('/reset')
public async reset(@Res() reply: FastifyReply) {
try {
await this.cacheManager.reset();
return reply.send('ok');
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Get('/get-query')
public async getQueryValue(
@Query('queryKey') queryKey: string,
@Res() reply: FastifyReply,
) {
try {
const value = await this.cacheManager.get(queryKey);
return reply.send(value);
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -1,20 +0,0 @@
import { ProxyController } from './proxy.controller';
import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-ioredis';
import type { RedisOptions } from 'ioredis';
import { env } from 'src/config/env';
@Module({
controllers: [ProxyController],
imports: [
CacheModule.register<RedisOptions>({
host: env.REDIS_HOST,
port: env.REDIS_PORT,
store: redisStore,
ttl: env.CACHE_TTL,
}),
],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class ProxyModule {}

View File

@ -1,11 +0,0 @@
export type GQLRequest = {
operationName: string;
query: string;
variables: string;
};
export type GQLResponse = {
data: unknown;
error?: unknown;
errors?: unknown[];
};

View File

@ -1,13 +0,0 @@
export function seconds() {
return {
fromDays(days: number) {
return days * 24 * 60 * 60;
},
fromHours(hours: number) {
return hours * 60 * 60;
},
fromMinutes(minutes: number) {
return minutes * 60;
},
};
}

View File

@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@ -1,22 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
},
"exclude": ["node_modules"]
}

View File

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

View File

@ -1,14 +1,8 @@
const { createConfig } = require('@vchikalkin/eslint-config-awesome');
module.exports = createConfig('next-typescript', {
module.exports = {
root: true,
extends: ['custom', 'custom/rules'],
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
'import/no-duplicates': 'off',
'react/forbid-component-props': 'off',
'import/consistent-type-specifier-style': 'off',
},
ignorePatterns: ['*.config.js', '.eslintrc.js'],
});
};

4
apps/web/.gitignore vendored
View File

@ -1,4 +0,0 @@
# Sentry Config File
.sentryclirc
/styles/antd.min.css

View File

@ -1,6 +1,6 @@
overwrite: true
schema: './graphql/crm.schema.graphql'
documents: [./**/!(*.schema).graphql]
documents: '**/*.{graphql,js,ts,jsx,tsx}'
generates:
./graphql/crm.types.ts:
plugins:
@ -16,7 +16,7 @@ generates:
object: true
defaultValue: true
scalars:
UUID: string
Uuid: string
Decimal: number
DateTime: string
# exclude: './graphql/crm.schema.graphql'

View File

@ -1,19 +0,0 @@
import elementsToValues from '@/Components/Calculation/config/map/values';
export const ERR_ELT_KASKO = 'ERR_ELT_KASKO';
export const ERR_ELT_OSAGO = 'ERR_ELT_OSAGO';
export const ERR_FINGAP_TABLE = 'ERR_FINGAP_TABLE';
export const ERR_INSURANCE_TABLE = 'ERR_INSURANCE_TABLE';
export const ERR_PAYMENTS_TABLE = 'ERR_PAYMENTS_TABLE';
export const ERROR_TABLE_KEYS = [
ERR_ELT_KASKO,
ERR_ELT_OSAGO,
ERR_FINGAP_TABLE,
ERR_INSURANCE_TABLE,
ERR_PAYMENTS_TABLE,
];
export const ERROR_ELEMENTS_KEYS = Object.keys(elementsToValues);
export const ERROR_KEYS = [...ERROR_ELEMENTS_KEYS, ...ERROR_TABLE_KEYS];

View File

@ -1,77 +0,0 @@
import * as cacheApi from '@/api/cache/query';
import { min } from '@/styles/mq';
import { useQuery } from '@tanstack/react-query';
import { memo, useState } from 'react';
import styled from 'styled-components';
import { Button, Collapse } from 'ui/elements';
import { Flex } from 'ui/grid';
type QueryProps = {
readonly onDeleteQuery: () => Promise<void>;
readonly queryKey: string;
};
const StyledPre = styled.pre`
max-height: 300px;
overflow-y: auto;
${min('desktop')} {
max-height: 800px;
}
`;
export const Query = memo(({ onDeleteQuery, queryKey }: QueryProps) => {
const { data, refetch } = useQuery({
enabled: false,
queryFn: ({ signal }) => signal && cacheApi.getQueryValue(queryKey, { signal }),
queryKey: ['admin', 'cache', 'query', queryKey],
refetchOnWindowFocus: false,
});
const [activeKey, setActiveKey] = useState<string | undefined>(undefined);
const [deletePending, setDeletePending] = useState(false);
const content = (
<>
<StyledPre>{JSON.stringify(data, null, 2)}</StyledPre>
<Flex justifyContent="flex-end">
<Button
type="primary"
danger
disabled={deletePending}
onClick={() => {
setDeletePending(true);
onDeleteQuery().finally(() => {
setDeletePending(false);
});
}}
>
Удалить
</Button>
</Flex>
</>
);
return (
<Collapse
bordered={false}
activeKey={activeKey}
items={[
{
children: data ? content : 'Загрузка...',
key: queryKey,
label: queryKey,
},
]}
onChange={() => {
if (activeKey) {
setActiveKey(undefined);
} else {
setActiveKey(queryKey);
refetch();
}
}}
/>
);
});

View File

@ -1,22 +0,0 @@
import { Query } from './Query';
import * as cacheApi from '@/api/cache/query';
import { useState } from 'react';
import type { QueryItem } from 'shared/types/cache';
type QueryListProps = QueryItem;
export const QueryList = ({ queries }: QueryListProps) => {
const [deletedQueries, setDeletedQueries] = useState<QueryItem['queries']>([]);
function handleDeleteQuery(queryKey: string) {
return cacheApi
.deleteQuery(queryKey)
.then(() => setDeletedQueries([...deletedQueries, queryKey]));
}
const activeQueries = queries.filter((queryKey) => !deletedQueries.includes(queryKey));
return activeQueries.map((queryKey) => (
<Query key={queryKey} queryKey={queryKey} onDeleteQuery={() => handleDeleteQuery(queryKey)} />
));
};

View File

@ -1,24 +0,0 @@
import { useState } from 'react';
import { Button } from 'ui/elements';
import { ReloadOutlined } from 'ui/elements/icons';
export function ReloadButton({ onClick }: { readonly onClick: () => Promise<unknown> }) {
const [pending, setPending] = useState(false);
return (
<Button
loading={pending}
onClick={() => {
setPending(true);
onClick().finally(() => {
setTimeout(() => {
setPending(false);
}, 1000);
});
}}
icon={<ReloadOutlined rev="" />}
>
Обновить
</Button>
);
}

View File

@ -1,70 +0,0 @@
import Background from '../../Layout/Background';
import { useFilteredQueries } from './lib/hooks';
import { QueryList } from './QueryList';
import { reset } from '@/api/cache/query';
import { min } from '@/styles/mq';
import styled from 'styled-components';
import { Button, Collapse, Divider, Input } from 'ui/elements';
const Wrapper = styled(Background)`
padding: 4px 6px;
width: 100vw;
${min('tablet')} {
min-height: 790px;
}
${min('laptop')} {
padding: 4px 18px 10px;
width: 1280px;
}
`;
const Flex = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`;
const ButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
`;
export function Cache() {
const { filteredQueries, refetch, setFilterString } = useFilteredQueries();
function handleDeleteQuery() {
return reset().then(() => refetch());
}
if (!filteredQueries) {
return <div>Загрузка...</div>;
}
return (
<Wrapper>
<Divider>Управление кэшем</Divider>
<Flex>
<Input
placeholder="Поиск по запросу"
allowClear
onChange={(e) => setFilterString(e.target.value)}
/>
<Collapse
accordion
items={Object.keys(filteredQueries).map((queryGroupName) => ({
children: <QueryList {...filteredQueries[queryGroupName]} />,
key: queryGroupName,
label: queryGroupName,
}))}
/>
<ButtonWrapper>
<Button type="primary" danger disabled={false} onClick={() => handleDeleteQuery()}>
Очистить кэш
</Button>
</ButtonWrapper>
</Flex>
</Wrapper>
);
}

View File

@ -1,30 +0,0 @@
import { filterQueries } from './utils';
import * as cacheApi from '@/api/cache/query';
import type { ResponseQueries } from '@/api/cache/types';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useDebounce } from 'use-debounce';
export function useFilteredQueries() {
const { data: queries, refetch } = useQuery({
enabled: false,
queryFn: ({ signal }) => signal && cacheApi.getQueries({ signal }),
queryKey: ['admin', 'cache', 'queries'],
refetchOnWindowFocus: false,
});
const [filteredQueries, setFilteredQueries] = useState<ResponseQueries | undefined>(queries);
const [filterString, setFilterString] = useState('');
const [debouncedFilterString] = useDebounce(filterString, 350);
useEffect(() => {
if (!debouncedFilterString) {
setFilteredQueries(queries);
}
if (queries && debouncedFilterString) {
setFilteredQueries(filterQueries(queries, debouncedFilterString));
}
}, [debouncedFilterString, queries]);
return { filteredQueries, queries, refetch, setFilterString };
}

View File

@ -1,23 +0,0 @@
import type { ResponseQueries } from '@/api/cache/types';
export function filterQueries(queriesObj: ResponseQueries, searchStr: string): ResponseQueries {
const filteredObj: ResponseQueries = {};
for (const key in queriesObj) {
if (key.includes(searchStr)) {
filteredObj[key] = queriesObj[key];
} else {
const queries: string[] = [];
queriesObj[key].queries.forEach((queryKey) => {
if (queryKey.toLowerCase().includes(searchStr.toLowerCase())) {
queries.push(queryKey);
}
});
if (queries.length) {
filteredObj[key] = { ...queriesObj[key], queries };
}
}
}
return filteredObj;
}

View File

@ -1,17 +0,0 @@
import { min } from '@/styles/mq';
import type { PropsWithChildren } from 'react';
import styled from 'styled-components';
const Flex = styled.div`
display: flex;
flex-direction: column;
${min('laptop')} {
flex-direction: row;
justify-content: center;
}
`;
export function Layout({ children }: PropsWithChildren) {
return <Flex>{children}</Flex>;
}

View File

@ -1,2 +0,0 @@
export * from './Cache';
export * from './Layout';

View File

@ -7,7 +7,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 +26,6 @@ export const rows: FormTabRows = [
{
title: 'Доп. продукты',
},
[['selectTechnicalCard', 'selectInsNSIB'], { gridTemplateColumns: ['1fr', '1fr 2fr'] }],
[['selectTechnicalCard', 'selectInsNSIB'], { gridTemplateColumns: '1fr 2fr' }],
[['selectRequirementTelematic', 'selectTracker', 'selectTelematic']],
];

View File

@ -4,14 +4,9 @@ export const id = 'create-kp';
export const title = 'Создание КП';
export const rows: FormTabRows = [
[
['cbxPriceWithDiscount', 'cbxFullPriceWithDiscount'],
{ gridTemplateColumns: ['1fr', '1fr 1fr'] },
],
[['cbxQuotePriceWithFullVAT', 'cbxCostIncrease'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxInsurance', 'cbxRegistrationQuote'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxTechnicalCardQuote', 'cbxNSIB'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxQuoteRedemptionGraph', 'cbxShowFinGAP'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxQuoteShowAcceptLimit'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['tbxQuoteName', 'radioQuoteContactGender'], { gridTemplateColumns: ['1fr', '2fr 1fr'] }],
[['cbxPriceWithDiscount', 'cbxFullPriceWithDiscount', 'cbxCostIncrease']],
[['cbxInsurance', 'cbxRegistrationQuote', 'cbxTechnicalCardQuote']],
[['cbxNSIB', 'cbxQuoteRedemptionGraph', 'cbxShowFinGAP']],
[['tbxQuoteName', 'radioQuoteContactGender'], { gridTemplateColumns: '1fr 1fr' }],
[['btnCreateKP', 'linkDownloadKp'], { gridTemplateColumns: '1fr 1fr' }],
];

View File

@ -1,60 +0,0 @@
import type { columns } from '../lib/config';
import type { Row, StoreSelector } from '../types';
import { message } from '@/Components/Common/Notification';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Table } from 'ui/elements';
export const PolicyTable = observer(
({
onSelectRow,
storeSelector,
...props
}: {
columns: typeof columns;
onSelectRow: (row: Row) => void;
storeSelector: StoreSelector;
}) => {
const { $process, $tables } = useStore();
const { getRows, getSelectedRow, setSelectedKey } = storeSelector($tables.elt);
return (
<Table
size="small"
pagination={false}
dataSource={getRows}
scroll={{
x: true,
}}
rowSelection={{
getCheckboxProps: (record) => ({
disabled:
!record.sum || record.status !== null || getRows.some((x) => x.status === 'fetching'),
}),
hideSelectAll: true,
onSelect: (record) => {
if (record.sum > 0) {
$process.add('ELT');
setSelectedKey(record.key);
onSelectRow(record);
message.success({
content: 'Выбранный расчет ЭЛТ применен',
duration: 1,
key: record.key,
onClick: () => message.destroy(record.key),
});
$process.delete('ELT');
}
},
selectedRowKeys: getSelectedRow ? [getSelectedRow.key] : [],
type: 'radio',
}}
expandable={{
expandedRowRender: (record) => record.message,
rowExpandable: (record) => Boolean(record.message),
}}
{...props}
/>
);
}
);

View File

@ -1,32 +0,0 @@
import type { StoreSelector } from '../types';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Button } from 'ui/elements';
import { ReloadOutlined } from 'ui/elements/icons';
import { Flex } from 'ui/grid';
export const ReloadButton = observer(
({ storeSelector, onClick }: { onClick: () => void; storeSelector: StoreSelector }) => {
const { $tables, $process } = useStore();
const { validation, getRows: rows } = storeSelector($tables.elt);
const hasErrors = validation.hasErrors;
return (
<Flex justifyContent="center">
<Button
onClick={onClick}
disabled={
hasErrors ||
$process.has('LoadKP') ||
$process.has('Calculate') ||
$process.has('CreateKP')
}
loading={rows.some((x) => x.status === 'fetching')}
shape="circle"
icon={<ReloadOutlined rev="" />}
/>
</Flex>
);
}
);

View File

@ -1,23 +0,0 @@
import type { StoreSelector } from '../types';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Alert } from 'ui/elements';
export const Validation = observer(({ storeSelector }: { storeSelector: StoreSelector }) => {
const { $tables, $process } = useStore();
const { validation } = storeSelector($tables.elt);
const errors = validation.getErrors();
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
}
return null;
});

View File

@ -1,3 +0,0 @@
export * from './PolicyTable';
export * from './ReloadButton';
export * from './Validation';

View File

@ -1,75 +0,0 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { PolicyTable, ReloadButton, Validation } from './Components';
import { columns } from './lib/config';
import { resetRow } from './lib/tools';
import type { Row, StoreSelector } from './types';
import { useStore } from '@/stores/hooks';
import { trpcClient } from '@/trpc/client';
import { observer } from 'mobx-react-lite';
import { Flex } from 'ui/grid';
const storeSelector: StoreSelector = ({ kasko }) => kasko;
export const Kasko = observer(() => {
const store = useStore();
const { $calculation, $tables } = store;
const calculateKasko = trpcClient.eltKasko.useMutation({
onError() {
$tables.elt.kasko.setRows(
$tables.elt.kasko.getRows.map((row) => ({ ...row, status: 'error' }))
);
},
onMutate: () => {
const rows = $tables.elt.kasko.getRows;
$tables.elt.kasko.setRows(rows.map((row) => ({ ...resetRow(row), status: 'fetching' })));
},
onSuccess: ({ rows }) => {
$tables.elt.kasko.setRows(rows);
},
});
function handleOnClick() {
calculateKasko.mutate({
calculation: {
values: store.$calculation.$values.getValues(),
},
});
}
function handleOnSelectRow(row: Row) {
$tables.insurance.row('kasko').column('insuranceCompany').setValue(row.key);
$tables.insurance.row('kasko').column('insCost').setValue(row.sum);
$calculation.element('tbxInsFranchise').setValue(row.totalFranchise);
}
type Column = (typeof columns)[number];
const kaskoColumns = columns.map((column: Column) => {
if (column.key === 'name') {
return {
...column,
title: 'Страховая компания КАСКО',
};
}
if (column.key === 'status') {
return {
...column,
title: <ReloadButton storeSelector={storeSelector} onClick={() => handleOnClick()} />,
};
}
return column;
});
return (
<Flex flexDirection="column">
<Validation storeSelector={storeSelector} />
<PolicyTable
storeSelector={storeSelector}
columns={kaskoColumns}
onSelectRow={(row) => handleOnSelectRow(row)}
/>
</Flex>
);
});

View File

@ -1,80 +0,0 @@
/* eslint-disable no-negated-condition */
/* eslint-disable sonarjs/cognitive-complexity */
import { PolicyTable, ReloadButton, Validation } from './Components';
import { columns } from './lib/config';
import { resetRow } from './lib/tools';
import type { Row, StoreSelector } from './types';
import { useStore } from '@/stores/hooks';
import { trpcClient } from '@/trpc/client';
import { observer } from 'mobx-react-lite';
import { Flex } from 'ui/grid';
const storeSelector: StoreSelector = ({ osago }) => osago;
export const Osago = observer(() => {
const store = useStore();
const { $tables } = store;
const calculateOsago = trpcClient.eltOsago.useMutation({
onError() {
$tables.elt.osago.setRows(
$tables.elt.osago.getRows.map((row) => ({ ...row, status: 'error' }))
);
},
onMutate: () => {
const rows = $tables.elt.osago.getRows;
$tables.elt.osago.setRows(rows.map((row) => ({ ...resetRow(row), status: 'fetching' })));
},
onSuccess: ({ rows }) => {
$tables.elt.osago.setRows(rows);
},
});
async function handleOnClick() {
calculateOsago.mutate({
calculation: {
values: store.$calculation.$values.getValues(),
},
});
}
function handleOnSelectRow(row: Row) {
$tables.insurance.row('osago').column('insuranceCompany').setValue(row.key);
$tables.insurance.row('osago').column('insCost').setValue(row.sum);
}
type Column = (typeof columns)[number];
const osagoColumns = columns.map((column: Column) => {
if (column.key === 'name') {
return {
...column,
title: 'Страховая компания ОСАГО',
};
}
if (column.key === 'status') {
return {
...column,
title: <ReloadButton storeSelector={storeSelector} onClick={() => handleOnClick()} />,
};
}
if (column.key === 'totalFranchise') {
return {
...column,
render: () => 'Не требуется',
};
}
return column;
});
return (
<Flex flexDirection="column">
<Validation storeSelector={storeSelector} />
<PolicyTable
storeSelector={storeSelector}
columns={osagoColumns}
onSelectRow={(row) => handleOnSelectRow(row)}
/>
</Flex>
);
});

View File

@ -1,20 +0,0 @@
import { Kasko } from './Kasko';
import { Osago } from './Osago';
const id = 'elt';
const title = 'ЭЛТ';
function ELT() {
return (
<>
<Osago />
<Kasko />
</>
);
}
export default {
Component: ELT,
id,
title,
};

View File

@ -1,56 +0,0 @@
import type { Row } from '../types';
import type { ColumnsType } from 'antd/lib/table';
import { CloseOutlined, LoadingOutlined } from 'ui/elements/icons';
import { Flex } from 'ui/grid';
const formatter = Intl.NumberFormat('ru', {
currency: 'RUB',
style: 'currency',
}).format;
export const columns: ColumnsType<Row> = [
{
dataIndex: 'name',
key: 'name',
title: 'Страховая компания',
width: '50%',
},
{
dataIndex: 'sum',
key: 'sum',
render: formatter,
sortDirections: ['descend', 'ascend'],
sorter: (a, b) => a.sum - b.sum,
title: 'Сумма',
width: '20%',
},
{
dataIndex: 'totalFranchise',
key: 'totalFranchise',
render: formatter,
title: 'Франшиза',
width: '20%',
},
{
dataIndex: 'status',
key: 'status',
render: (_, record) => {
if (record.status === 'fetching')
return (
<Flex justifyContent="center">
<LoadingOutlined spin rev="" />
</Flex>
);
if (record.status === 'error')
return (
<Flex justifyContent="center">
<CloseOutlined rev="" />
</Flex>
);
return false;
},
title: undefined,
width: '5%',
},
];

View File

@ -1,12 +0,0 @@
import type { Row } from '@/Components/Calculation/Form/ELT/types';
import { defaultRow } from '@/stores/tables/elt/default-values';
export function resetRow(row: Row): Row {
return {
...defaultRow,
id: row.id,
key: row.key,
metodCalc: row.metodCalc,
name: row.name,
};
}

View File

@ -1,7 +0,0 @@
import type { RowSchema } from '@/config/schema/elt';
import type ELTStore from '@/stores/tables/elt';
import type PolicyStore from '@/stores/tables/elt/policy';
import type { z } from 'zod';
export type Row = z.infer<typeof RowSchema>;
export type StoreSelector = (eltStore: ELTStore) => PolicyStore;

View File

@ -3,46 +3,38 @@ import { useStore } from '@/stores/hooks';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { Alert, Table } from 'ui/elements';
import { Flex } from 'ui/grid';
import { Flex } from 'ui';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
const Grid = styled(Flex)`
flex-direction: column;
`;
const Validation = observer(() => {
const { $tables, $process } = useStore();
const store = useStore();
const errors = $tables.fingap.validation.getErrors();
const errors = store.$tables.fingap.validation.getErrors();
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
return <Alert type="error" banner message={errors[0].message} />;
}
return null;
});
const FinGAP = observer(() => {
const { $tables, $process } = useStore();
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 }),
onChange: (_, selectedRows) => {
const selectedKeys = selectedRows.reduce((acc, row) => {
acc.push(row.key);

View File

@ -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 options={options} setValue={setValue} status={statuses} value={value} {...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 setValue={setValue} status={statuses} value={value} {...props} />;
});
}

View File

@ -1,60 +1,27 @@
/* eslint-disable react/forbid-component-props */
/* eslint-disable canonical/sort-keys */
import { buildOptionComponent, buildValueComponent } from './builders';
import type * as Insurance from './types';
import { MAX_INSURANCE } from '@/constants/values';
import { useStore } from '@/stores/hooks';
import type { ColumnsType } from 'antd/lib/table';
import { observer } from 'mobx-react-lite';
import { parser } from 'tools/number';
import { InputNumber, Select } from 'ui/elements';
import { CheckOutlined } from 'ui/elements/icons';
import { createFormatter } from 'ui/elements/InputNumber';
import { formatter, parser } from 'tools/number';
import InputNumber from 'ui/elements/InputNumber';
import Select from 'ui/elements/Select';
export const columns: ColumnsType<Insurance.RowValues> = [
{
key: 'elt',
dataIndex: 'elt',
render: (_, record) => {
const Check = observer(() => {
const { $tables } = useStore();
if (
(record.key === 'osago' && $tables.elt.osago.getSelectedRow?.key) ||
(record.key === 'kasko' && $tables.elt.kasko.getSelectedRow?.key)
) {
return <CheckOutlined rev="" />;
}
return null;
});
return <Check />;
},
title: 'ЭЛТ',
width: '1%',
},
{
key: 'policyType',
dataIndex: 'policyType',
title: 'Тип полиса',
width: '11%',
},
{
key: 'insuranceCompany',
dataIndex: 'insuranceCompany',
title: 'Страховая компания',
width: 300,
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insuranceCompany');
return (
<Component
optionFilterProp="label"
showSearch
style={{
width: '100%',
}}
/>
);
return <Component optionFilterProp="label" showSearch />;
},
},
{
@ -64,40 +31,28 @@ export const columns: ColumnsType<Insurance.RowValues> = [
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insured');
return (
<Component
style={{
width: '100%',
maxWidth: '100px',
}}
/>
);
return <Component />;
},
width: '100px',
},
{
key: 'insCost',
dataIndex: 'insCost',
title: 'Сумма за 1-й период',
title: 'Стоимость за 1-й период',
render: (_, record) => {
const Component = buildValueComponent(record.key, InputNumber, 'insCost');
return (
<Component
addonAfter="₽"
formatter={createFormatter({ minimumFractionDigits: 2, maximumFractionDigits: 2 })}
formatter={formatter}
max={MAX_INSURANCE}
min={0}
parser={parser}
precision={2}
step={1000}
style={{
width: '150px',
}}
/>
);
},
width: '150px',
},
{
key: 'insTerm',
@ -106,15 +61,7 @@ export const columns: ColumnsType<Insurance.RowValues> = [
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insTerm');
return (
<Component
style={{
width: '150px',
maxWidth: '150px',
}}
/>
);
return <Component />;
},
width: '150px',
},
];

View File

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

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 { BaseOption, Status } from 'ui/elements/types';
import type { z } from 'zod';
export type Keys = z.infer<typeof KeysSchema>;

View File

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

View File

@ -1,15 +1,15 @@
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';
import { Flex } from 'ui';
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>

View File

@ -8,8 +8,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

@ -1,5 +1,4 @@
import type { FormTabRows } from '../../lib/render-rows';
import { transformRowsForMobile } from '../lib/utils';
export const id = 'leasing-object';
export const title = 'ПЛ';
@ -17,5 +16,3 @@ export const rows: FormTabRows = [
[['selectLeaseObjectCategory', 'tbxEngineVolume', 'tbxMileage']],
[['tbxMaxMass', 'tbxEngineHours', 'tbxVIN']],
];
export const mobileRows = transformRowsForMobile(rows);

View File

@ -1,14 +1,8 @@
import renderFormRows from '../../lib/render-rows';
import { id, mobileRows, rows, title } from './config';
import { Media } from '@/styles/media';
import { id, rows, title } from './config';
function LeasingObject() {
return (
<>
<Media lessThan="laptop">{renderFormRows(mobileRows)}</Media>
<Media greaterThanOrEqual="laptop">{renderFormRows(rows)}</Media>
</>
);
return renderFormRows(rows);
}
export default {

View File

@ -2,7 +2,7 @@ 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';
import { Flex } from 'ui';
function PaymentsParams() {
const renderedElements = elements.map((elementName) => {

View File

@ -1,4 +1,4 @@
import { usePaymentSum, usePaymentValue } from './hooks';
import { usePaymentValue } from './hooks';
import { useRowStatus } from '@/stores/tables/payments/hooks';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
@ -8,19 +8,6 @@ export function buildValueComponent<T>(index: number, Component: ComponentType<T
const [value, setValue] = usePaymentValue(index);
const status = useRowStatus(index);
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
});
}
export function buildSumComponent<T>(index: number, Component: ComponentType<T>) {
return observer((props: T) => {
const [value, setValue] = usePaymentSum(index);
const status = useRowStatus(index);
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
return <Component setValue={setValue} status={status} value={value} {...props} />;
});
}

View File

@ -1,23 +1,14 @@
/* eslint-disable react/forbid-component-props */
/* eslint-disable canonical/sort-keys */
import { buildSumComponent, buildValueComponent } from './builders';
import { buildValueComponent } from './builders';
import type { ColumnsType } from 'antd/lib/table';
import { parser } from 'tools/number';
import { InputNumber } from 'ui/elements';
import { createFormatter } from 'ui/elements/InputNumber';
import InputNumber from 'ui/elements/InputNumber';
type Payment = {
key: number;
num: number;
paymentRelation: number;
paymentSum: number;
};
const formatter = createFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
export const columns: ColumnsType<Payment> = [
{
key: 'num',
@ -33,38 +24,7 @@ export const columns: ColumnsType<Payment> = [
render: (_value, payment) => {
const Component = buildValueComponent(payment.num, InputNumber);
return (
<Component
max={100}
min={0}
precision={payment.num === 0 ? 4 : 2}
step={1}
style={{
width: '100%',
}}
/>
);
},
},
{
key: 'paymentSum',
dataIndex: 'paymentSum',
title: 'Сумма',
render: (_value, payment) => {
const Component = buildSumComponent(payment.num, InputNumber);
return (
<Component
min={0}
precision={2}
step={1000}
formatter={formatter}
parser={parser}
style={{
width: '100%',
}}
/>
);
return <Component max={100} min={0} precision={payment.num === 0 ? 4 : 2} step={1} />;
},
},
];

View File

@ -1,4 +1,4 @@
import { useRowSum, useRowValue } from '@/stores/tables/payments/hooks';
import { useRowValue } from '@/stores/tables/payments/hooks';
import { useEffect, useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
@ -20,22 +20,3 @@ export function usePaymentValue(index) {
return [value, setValue];
}
export function usePaymentSum(index) {
const [storeValue, setStoreValue] = useRowSum(index);
const [value, setValue] = useState(storeValue);
const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, 350, { maxWait: 1000 });
useEffect(() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
}, [value]);
useEffect(() => {
setValue(storeValue);
}, [storeValue]);
return [value, setValue];
}

View File

@ -3,11 +3,10 @@ import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { computed } from 'mobx';
import { observer } from 'mobx-react-lite';
import { createContext, useContext, useMemo, useState } from 'react';
import styled from 'styled-components';
import { Alert, Segmented, Table } from 'ui/elements';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Box, Flex } from 'ui/grid';
import { useDebouncedCallback } from 'use-debounce';
const Grid = styled(Flex)`
flex-direction: column;
@ -31,26 +30,18 @@ const TablesGroupGrid = styled(Box)`
`;
const Validation = observer(() => {
const { $tables, $process } = useStore();
const { payments } = $tables;
const store = useStore();
const { payments } = store.$tables;
const errors = payments.validation.getErrors();
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
return <Alert type="error" banner message={errors[0].message} />;
}
return null;
});
export const ModeContext = createContext('paymentRelation');
const SPLIT_NUMBER = 12;
function TablePart({ num }) {
@ -59,25 +50,16 @@ function TablePart({ num }) {
const values = payments.values.slice(num * SPLIT_NUMBER, num * SPLIT_NUMBER + SPLIT_NUMBER);
const dataSource = values.map((_, index) => ({
const dataSource = values.map((value, index) => ({
key: index + num * SPLIT_NUMBER,
num: index + num * SPLIT_NUMBER,
paymentRelation: value,
}));
const mode = useContext(ModeContext);
return useMemo(
() => (
<TableWrapper>
<Table
size="small"
columns={columns.filter((x) => ['num', mode].includes(x.key))}
dataSource={dataSource}
pagination={false}
/>
</TableWrapper>
),
[dataSource, mode]
return (
<TableWrapper>
<Table size="small" columns={columns} dataSource={dataSource} pagination={false} />
</TableWrapper>
);
}
@ -97,36 +79,11 @@ const TablesGroup = observer(() => {
return <TablesGroupGrid>{tables}</TablesGroupGrid>;
});
function Mode({ setMode }) {
const { $process } = useStore();
if (!$process.has('Unlimited')) {
return false;
}
return (
<Segmented
block
options={[
{ label: 'Процент', value: 'paymentRelation' },
{ label: 'Сумма', value: 'paymentSum' },
]}
onChange={(value) => setMode(value)}
/>
);
}
export default function TablePayments() {
const [mode, setMode] = useState('paymentRelation');
const debouncedSetMode = useDebouncedCallback(setMode, 300);
return (
<Grid>
<ModeContext.Provider value={mode}>
<Validation />
<Mode setMode={debouncedSetMode} />
<TablesGroup />
</ModeContext.Provider>
<Validation />
<TablesGroup />
</Grid>
);
}

View File

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

View File

@ -1,14 +1,8 @@
import renderFormRows from '../../lib/render-rows';
import { id, mobileRows, rows, title } from './config';
import { Media } from '@/styles/media';
import { id, rows, title } from './config';
function Leasing() {
return (
<>
<Media lessThan="laptop">{renderFormRows(mobileRows)}</Media>
<Media greaterThanOrEqual="laptop">{renderFormRows(rows)}</Media>
</>
);
return renderFormRows(rows);
}
export default {

View File

@ -1,14 +0,0 @@
import type { FormTabRows } from '../../lib/render-rows';
export const id = 'unlimited';
export const title = 'Без ограничений';
export const rows: FormTabRows = [
[['cbxDisableChecks', 'selectUser']],
[['selectTarif', 'tbxCreditRate', 'selectRate']],
[['tbxMinPriceChange', 'tbxMaxPriceChange']],
[['tbxImporterRewardPerc', 'tbxImporterRewardRub']],
[['tbxBonusCoefficient', 'tbxComissionRub', 'tbxComissionPerc']],
[['cbxSupplierFinancing', 'cbxPartialVAT', 'cbxFloatingRate']],
[['cbxQuotePriceWithFullVAT']],
];

View File

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

View File

@ -1,30 +1,16 @@
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';
import Tabs from 'ui/elements/layout/Tabs';
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

@ -1,32 +0,0 @@
/**
*
* @param {import('../../lib/render-rows').FormTabRows} rows
* @returns {import('../../lib/render-rows').FormTabRows}
*/
export function transformRowsForMobile(rows) {
const mobileRows = [];
let columnGroups = {};
rows.forEach((row) => {
if (Array.isArray(row)) {
row[0].forEach((item, index) => {
if (!columnGroups[index]) {
columnGroups[index] = [];
}
columnGroups[index].push(item);
});
} else {
Object.values(columnGroups).forEach((group) => {
mobileRows.push([group, { gridTemplateColumns: '1fr' }]);
});
columnGroups = {};
mobileRows.push(row);
}
});
Object.values(columnGroups).forEach((group) => {
mobileRows.push([group, { gridTemplateColumns: '1fr' }]);
});
return mobileRows;
}

View File

@ -1,20 +0,0 @@
import { min } from '@/styles/mq';
import styled from 'styled-components';
import { Box } from 'ui/grid';
export const Layout = styled(Box)`
display: flex;
flex-direction: column;
gap: 10px;
${min('laptop')} {
display: grid;
align-items: flex-start;
grid-template-columns: 2fr 1fr;
}
${min('desktop')} {
grid-template-columns: 2fr 1fr 1.5fr;
/* margin: 8px 5%; */
}
`;

View File

@ -1,84 +0,0 @@
/* eslint-disable canonical/sort-keys */
import type { ResultPayment } from '@/stores/results/types';
import type { ColumnsType } from 'antd/lib/table';
export const columns: ColumnsType<ResultPayment> = [
{
key: 'num',
dataIndex: 'num',
title: '#',
width: '10%',
},
{
key: 'paymentSum',
dataIndex: 'paymentSum',
title: 'Сумма платежа',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: 'ndsCompensation',
dataIndex: 'ndsCompensation',
title: 'НДС к возмещению',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: 'redemptionAmount',
dataIndex: 'redemptionAmount',
title: 'Сумма досрочного выкупа',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_piColumn',
dataIndex: '_piColumn',
title: 'PI Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_cashflowMsfoWithCfColumn',
dataIndex: '_cashflowMsfoWithCfColumn',
title: 'CashflowMSFOWithCF Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_creditPaymentColumn',
dataIndex: '_creditPaymentColumn',
title: 'CreditPayment Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_cashflowMsfoColumn',
dataIndex: '_cashflowMsfoColumn',
title: 'CashflowMSFO Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_interestColumn',
dataIndex: '_interestColumn',
title: 'Interest Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
];

View File

@ -1,41 +0,0 @@
/* eslint-disable no-negated-condition */
import { columns } from './config';
import { MAX_LEASING_PERIOD } from '@/constants/values';
import { useStore } from '@/stores/hooks';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import { Table } from 'ui/elements';
const PaymentsTable = observer(() => {
const { $process, $results } = useStore();
const unlimited = $process.has('Unlimited');
const dataSource = toJS($results.payments);
const dataColumns = !unlimited ? columns.filter((x) => !x.key.includes('_')) : columns;
return (
<Table
columns={dataColumns}
dataSource={dataSource}
size="small"
pagination={{
defaultPageSize: MAX_LEASING_PERIOD,
hideOnSinglePage: true,
responsive: true,
// showLessItems: true,
showSizeChanger: false,
}}
scroll={{
x: dataColumns.length > 5 ? 1000 : undefined,
y: dataSource.length > 16 ? 630 : undefined,
}}
/>
);
});
export default {
Component: PaymentsTable,
id: 'payments-table',
title: 'Таблица платежей',
};

View File

@ -1,102 +0,0 @@
import type { ResultValues } from '@/stores/results/types';
export const id = 'output';
export const title = 'Результаты';
export const titles: Record<keyof ResultValues, string> = {
_resultContractEconomy: 'Экономика',
_resultContractEconomyWithVAT: 'Экономика, с НДС',
_resultPi: 'PI',
_resultPiRepayment: 'PI для досрочки',
_resultSumCredit: 'Сумма кредита',
_resultSumCreditPayment: 'Сумма платежей по кредиту',
_resultVatRecoverable: 'НДС к возмещению',
resultAB_FL: 'АВ ФЛ, без НДФЛ.',
resultAB_UL: 'АВ ЮЛ, с НДС.',
resultBonusDopProd: 'Бонус МПЛ за доп.продукты, без НДФЛ',
resultBonusMPL: 'Бонус МПЛ за лизинг, без НДФЛ',
resultBonusSafeFinance: 'Бонус за Safe Finance без НДФЛ',
resultDopMPLLeasing: 'Доп.бонус МПЛ за лизинг, без НДФЛ',
resultDopProdSum: 'Общая сумма доп.продуктов',
resultFirstPayment: 'Первый платеж',
resultFirstPaymentRiskPolicy: 'Первый платеж по риск политике, %',
resultIRRGraphPerc: 'IRR по графику клиента, %',
resultIRRNominalPerc: 'IRR (номинал), %',
resultInsKasko: 'КАСКО, НС, ДГО в графике',
resultInsOsago: 'ОСАГО в графике',
resultLastPayment: 'Последний платеж',
resultParticipationAmount: 'Сумма участия (для принятия решения)',
resultPlPrice: 'Стоимость ПЛ с НДС',
resultPriceUpPr: 'Удорожание, год',
resultTerm: 'Срок, мес.',
resultTotalGraphwithNDS: 'Итого по графику, с НДС',
};
const moneyFormatter = Intl.NumberFormat('ru', {
currency: 'RUB',
style: 'currency',
}).format;
const percentFormatter = Intl.NumberFormat('ru', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
style: 'percent',
}).format;
export const formatters = {
_resultContractEconomy: moneyFormatter,
_resultContractEconomyWithVAT: moneyFormatter,
_resultPi: percentFormatter,
_resultPiRepayment: percentFormatter,
_resultSumCredit: moneyFormatter,
_resultSumCreditPayment: moneyFormatter,
_resultVatRecoverable: moneyFormatter,
resultAB_FL: moneyFormatter,
resultAB_UL: moneyFormatter,
resultBonusDopProd: moneyFormatter,
resultBonusMPL: moneyFormatter,
resultBonusSafeFinance: moneyFormatter,
resultDopMPLLeasing: moneyFormatter,
resultDopProdSum: moneyFormatter,
resultFirstPayment: moneyFormatter,
resultFirstPaymentRiskPolicy: percentFormatter,
resultIRRGraphPerc: percentFormatter,
resultIRRNominalPerc: percentFormatter,
resultInsKasko: moneyFormatter,
resultInsOsago: moneyFormatter,
resultLastPayment: moneyFormatter,
resultParticipationAmount: moneyFormatter,
resultPlPrice: moneyFormatter,
resultPriceUpPr: percentFormatter,
resultTerm: Intl.NumberFormat('ru').format,
resultTotalGraphwithNDS: moneyFormatter,
};
export const elements: Array<keyof ResultValues> = [
'_resultContractEconomy',
'_resultContractEconomyWithVAT',
'_resultPi',
'_resultPiRepayment',
'_resultSumCredit',
'_resultSumCreditPayment',
'_resultVatRecoverable',
'resultTotalGraphwithNDS',
'resultPlPrice',
'resultPriceUpPr',
'resultIRRGraphPerc',
'resultIRRNominalPerc',
'resultInsKasko',
'resultInsOsago',
'resultDopProdSum',
'resultFirstPayment',
'resultLastPayment',
'resultFirstPaymentRiskPolicy',
'resultTerm',
'resultAB_FL',
'resultAB_UL',
'resultBonusMPL',
'resultDopMPLLeasing',
'resultBonusDopProd',
'resultBonusSafeFinance',
'resultParticipationAmount',
];

View File

@ -1,55 +0,0 @@
import { elements, formatters, id, title, titles } from './config';
import { Container, Head } from '@/Components/Layout/Element';
import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { Text } from 'ui/elements';
import { Box } from 'ui/grid';
const Grid = styled(Box)`
display: grid;
grid-template-columns: 1fr;
${min('tablet')} {
grid-template-columns: 1fr 1fr;
}
`;
const Wrapper = styled.div`
margin-bottom: 18px;
`;
const Results = observer(() => {
const { $process, $results } = useStore();
const resultsValues = toJS($results.values);
// eslint-disable-next-line no-negated-condition
const values = !$process.has('Unlimited') ? elements.filter((x) => !x.startsWith('_')) : elements;
return (
<Grid>
{values.map((valueName) => {
const formatter = formatters[valueName];
const storeValue = resultsValues[valueName];
const value = formatter(storeValue);
return (
<Wrapper key={valueName}>
<Container key={valueName}>
<Head title={titles[valueName]} />
<Text>{value}</Text>
</Container>
</Wrapper>
);
})}
</Grid>
);
});
export default {
Component: Results,
id,
title,
};

View File

@ -1,71 +0,0 @@
import PaymentsTable from './PaymentsTable';
import Results from './Results';
import Validation from './Validation';
import Background from '@/Components/Layout/Background';
import { useErrors, useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { observer } from 'mobx-react-lite';
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { Badge, Tabs } from 'ui/elements';
const outputTabs = [PaymentsTable, Results, Validation];
const items = outputTabs.map(({ Component, id, title }) => {
let Label = () => title;
if (id === 'validation') {
Label = observer(() => {
const { hasErrors } = useErrors();
return (
<Badge offset={[5, 0]} dot={hasErrors}>
{title}
</Badge>
);
});
}
return {
children: <Component />,
key: id,
label: <Label />,
};
});
const Wrapper = styled(Background)`
padding: 4px 10px;
min-height: 200px;
${min('laptop')} {
padding: 4px 18px;
min-height: 790px;
}
`;
export const Output = observer(({ tabs }) => {
const { $results } = useStore();
const [activeKey, setActiveKey] = useState(undefined);
const { hasErrors } = useErrors();
useEffect(() => {
if ($results.payments.length > 0) {
setActiveKey('payments-table');
}
if (!tabs && hasErrors) {
setActiveKey('validation');
}
}, [$results.payments.length, hasErrors, tabs]);
return (
<Wrapper>
<Tabs
items={tabs ? items.filter((x) => x.key !== 'validation') : items}
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
}}
/>
</Wrapper>
);
});

View File

@ -2,61 +2,17 @@ import type { FormTabRows } from '../lib/render-rows';
const defaultRowStyle = { gridTemplateColumns: '1fr' };
export const mainRows: FormTabRows = [
export const rows: FormTabRows = [
{ title: 'Выбор Интереса/ЛС' },
[['selectLead'], defaultRowStyle],
[['selectOpportunity'], defaultRowStyle],
[['cbxRecalcWithRevision'], defaultRowStyle],
[['selectQuote'], defaultRowStyle],
[
['btnCalculate'],
{
gridTemplateColumns: '1fr',
marginBottom: ['5px'],
},
],
[
['btnCreateKP', 'linkDownloadKp'],
{
gap: ['10px'],
gridTemplateColumns: ['1fr 1fr'],
},
],
];
[['btnCalculate'], defaultRowStyle],
export const unlimitedMainRows: FormTabRows = [
{ title: 'Выбор Интереса/ЛС' },
[['selectUser'], defaultRowStyle],
[['selectLead'], defaultRowStyle],
[['selectOpportunity'], defaultRowStyle],
[['cbxRecalcWithRevision'], defaultRowStyle],
[['selectQuote'], defaultRowStyle],
[
['btnCalculate'],
{
gridTemplateColumns: '1fr',
marginBottom: ['5px'],
},
],
[
['btnCreateKP', 'linkDownloadKp'],
{
gap: ['10px'],
gridTemplateColumns: ['1fr 1fr'],
},
],
];
export const paramsRows: FormTabRows = [
{ title: 'Параметры расчета' },
[['labelIrrInfo'], defaultRowStyle],
[['radioCalcType'], defaultRowStyle],
[['tbxIRR_Perc'], defaultRowStyle],
[['tbxTotalPayments'], defaultRowStyle],
];
export const unlimitedParamsRows: FormTabRows = [
{ title: 'Параметры расчета' },
[['radioCalcType'], defaultRowStyle],
[['tbxIRR_Perc', 'tbxPi'], { gridTemplateColumns: '1fr 1fr' }],
[['tbxTotalPayments'], defaultRowStyle],
];

View File

@ -1,9 +1,7 @@
import renderFormRows from '../lib/render-rows';
import * as config from './config';
import { rows } from './config';
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';
const Wrapper = styled(Background)`
@ -18,20 +16,6 @@ const Wrapper = styled(Background)`
}
`;
export const Settings = memo(() => {
const { $process } = useStore();
const mainRows = $process.has('Unlimited')
? renderFormRows(config.unlimitedMainRows)
: renderFormRows(config.mainRows);
const paramsRows = $process.has('Unlimited')
? renderFormRows(config.unlimitedParamsRows)
: renderFormRows(config.paramsRows);
return (
<Wrapper>
{mainRows}
{paramsRows}
</Wrapper>
);
});
export default function Settings() {
return <Wrapper>{renderFormRows(rows)}</Wrapper>;
}

View File

@ -1,19 +0,0 @@
import Validation from '../Output/Validation';
import Background from '@/Components/Layout/Background';
import { min } from '@/styles/mq';
import { memo } from 'react';
import styled from 'styled-components';
const Wrapper = styled(Background)`
padding: 4px 10px;
${min('laptop')} {
padding: 4px 18px;
}
`;
export const Component = memo(() => (
<Wrapper>
<Validation.Component />
</Wrapper>
));

View File

@ -1,31 +0,0 @@
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { LoadingOutlined } from 'ui/elements/icons';
const TextAddon = styled.span`
font-size: 14px;
`;
const formatter = Intl.NumberFormat('ru', {
minimumFractionDigits: 2,
}).format;
export const IRRAddon = observer(() => {
const { $calculation, $process } = useStore();
if ($process.has('Tarif')) {
return (
<TextAddon>
<LoadingOutlined rev="" />
{' Подбирается тариф...'}
</TextAddon>
);
}
const tarif = $calculation.element('selectTarif').getValue();
if (!tarif) return <TextAddon>Тариф не найден</TextAddon>;
const { min, max } = $calculation.$values.getValue('irrInfo');
return <TextAddon>{`${formatter(min)}% - ${formatter(max)}%`}</TextAddon>;
});

View File

@ -1,53 +0,0 @@
/* eslint-disable react/forbid-component-props */
import titles from '../config/elements-titles';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { pick } from 'radash';
import styled from 'styled-components';
import { Tag } from 'ui/elements';
const Container = styled.div`
display: flex;
flex-direction: row;
gap: 5px;
`;
const TagWrapper = styled.div<{ disabled: boolean }>`
> span {
pointer-events: ${(props) => (props.disabled ? 'none' : 'auto')};
opacity: ${(props) => (props.disabled ? '50%' : '')};
}
`;
const tagsData = pick(titles, ['cbxPartialVAT', 'cbxFloatingRate']);
const { CheckableTag } = Tag;
export const ProductAddon = observer(() => {
const { $calculation } = useStore();
function handleChange(elementName: keyof typeof tagsData, checked: boolean) {
$calculation.element(elementName).setValue(checked);
}
return (
<Container>
{(Object.keys(tagsData) as Array<keyof typeof tagsData>).map((elementName) => {
const visible = $calculation.$status.getStatus(elementName);
return (
<TagWrapper key={elementName} disabled={visible === 'Disabled'}>
<CheckableTag
checked={$calculation.element(elementName).getValue()}
onChange={(checked) => handleChange(elementName, checked)}
key={elementName}
style={{ marginInlineEnd: 0 }}
>
{tagsData[elementName]}
</CheckableTag>
</TagWrapper>
);
})}
</Container>
);
});

View File

@ -1,39 +1,24 @@
import type { Elements } from '../config/map/actions';
import { useProcessContext } from '@/process/hooks/common';
import { useStatus } from '@/stores/calculation/statuses/hooks';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useThrottledCallback } from 'use-debounce';
type BuilderProps = {
elementName: Elements;
valueName: string;
};
export function buildAction<T>(
export default function buildAction<T>(
Component: ComponentType<T>,
{ elementName, valueName: processName }: BuilderProps
) {
return observer((props: T) => {
const status = useStatus(elementName);
const context = useProcessContext();
const throttledAction = useThrottledCallback(
() => {
import(`process/${processName}/action`).then((module) => module.action(context));
},
1200,
{
trailing: false,
}
);
return (
<Component
onClick={throttledAction}
action={() => import(`process/${processName}/action`).then((module) => module.action())}
status={status}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
{...props}
/>
);

View File

@ -1,38 +0,0 @@
import type { Elements } from '../config/map/values';
import { useStoreValue } from './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 { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { Form } from 'ui/elements';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildCheck<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={(event: CheckboxChangeEvent) => {
setValue(event.target.checked);
}}
disabled={status === 'Disabled'}
checked={value}
{...props}
/>
</Form.Item>
);
});
}

View File

@ -1,30 +0,0 @@
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';
export type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildLink<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value] = useStoreValue(valueName);
const status = useStatus(elementName);
return (
<Component
href={status === 'Disabled' ? undefined : value || undefined}
disabled={!value || status === 'Disabled'}
loading={status === 'Loading'}
{...props}
/>
);
});
}

View File

@ -6,34 +6,32 @@ import { useValidation } from '@/stores/calculation/validation/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { Form } from 'ui/elements';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildOptions<T>(
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 { validateStatus, help } = useValidation(elementName);
const { isValid, help } = useValidation(elementName);
const options = useOptions(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
disabled={status === 'Disabled'}
loading={status === 'Loading'}
options={options}
onChange={setValue}
value={value}
{...props}
/>
</Form.Item>
<Component
help={help}
isValid={isValid}
options={options}
setValue={setValue}
status={status}
value={value}
{...props}
/>
);
});
}

View File

@ -4,18 +4,13 @@ import { useValue } from '@/stores/calculation/values/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import styled from 'styled-components';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
const Wrapper = styled.div`
margin-bottom: 24px;
`;
export function buildReadonly<T>(
export default function buildReadonly<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
@ -23,10 +18,6 @@ export function buildReadonly<T>(
const [value] = useValue(valueName);
const status = useStatus(elementName);
return (
<Wrapper>
<Component readOnly status={status} value={value} {...props} />
</Wrapper>
);
return <Component readOnly status={status} value={value} {...props} />;
});
}

View File

@ -1,40 +0,0 @@
import type { Elements } from '../config/map/values';
import { useStoreValue } from './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 { SwitchChangeEventHandler } from 'antd/lib/switch';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { Form } from 'ui/elements';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildSwitch<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={
((checked) => {
setValue(checked);
}) as SwitchChangeEventHandler
}
disabled={status === 'Disabled'}
checked={value}
{...props}
/>
</Form.Item>
);
});
}

View File

@ -4,58 +4,31 @@ import { useStatus } from '@/stores/calculation/statuses/hooks';
import { useValidation } from '@/stores/calculation/validation/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ChangeEvent, ComponentType } from 'react';
import { Form } from 'ui/elements';
import type { ComponentType } from 'react';
type BuilderProps = {
export type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildValue<T>(
export default function buildValue<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
const { isValid, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
}}
disabled={status === 'Disabled'}
value={value}
{...props}
/>
</Form.Item>
);
});
}
export function buildNumberValue<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={(inputValue: number | null) => {
setValue(inputValue || 0);
}}
disabled={status === 'Disabled'}
value={value}
{...props}
/>
</Form.Item>
<Component
help={help}
isValid={isValid}
setValue={setValue}
status={status}
value={value}
{...props}
/>
);
});
}

View File

@ -0,0 +1,4 @@
export { default as buildAction } from './build-action';
export { default as buildOptions } from './build-options';
export { default as buildReadonly } from './build-readonly';
export { default as buildValue } from './build-value';

View File

@ -1,7 +0,0 @@
export * from './build-action';
export * from './build-check';
export * from './build-link';
export * from './build-options';
export * from './build-readonly';
export * from './build-switch';
export * from './build-value';

View File

@ -1,3 +1,4 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
@ -72,7 +73,7 @@ const components = wrapComponentsMap({
selectCalcFinDepartment: e.Select,
selectFinDepartmentRewardCondtion: e.Select,
tbxFinDepartmentRewardSumm: e.InputNumber,
cbxInsDecentral: e.Checkbox,
cbxInsDecentral: e.Switch,
tbxInsFranchise: e.InputNumber,
cbxInsUnlimitDrivers: e.Switch,
tbxInsAgeDrivers: e.InputNumber,
@ -130,13 +131,6 @@ const components = wrapComponentsMap({
tbxBonusCoefficient: e.InputNumber,
selectLeasingWithoutKasko: e.Select,
tbxVIN: e.Input,
selectUser: e.Select,
cbxSupplierFinancing: e.Switch,
tbxPi: e.InputNumber,
cbxPartialVAT: e.Switch,
cbxFloatingRate: e.Switch,
cbxQuotePriceWithFullVAT: e.Switch,
cbxQuoteShowAcceptLimit: e.Switch,
/** Readonly Elements */
labelLeaseObjectRisk: e.Text,
@ -148,7 +142,6 @@ const components = wrapComponentsMap({
/** Button Elements */
btnCreateKP: e.Button,
btnCreateKPMini: e.Button,
btnCalculate: e.Button,
/** Link Elements */

View File

@ -1,13 +1,11 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
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 });
import { formatter, formatterExtra, parser } from 'tools/number';
import { DownloadOutlined } from 'ui/elements/icons';
const props: Partial<ElementsProps> = {
tbxLeaseObjectPrice: {
@ -18,9 +16,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxLeaseObjectPriceWthtVAT: {
min: 0,
@ -30,9 +25,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxVATInLeaseObjectPrice: {
min: 0,
@ -42,18 +34,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,
@ -63,9 +49,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxSupplierDiscountPerc: {
min: 0,
@ -73,9 +56,6 @@ const props: Partial<ElementsProps> = {
precision: 2,
addonAfter: '%',
style: {
width: '100%',
},
},
radioBalanceHolder: {
optionType: 'button',
@ -88,9 +68,6 @@ const props: Partial<ElementsProps> = {
precision: 2,
addonAfter: '%',
style: {
width: '100%',
},
},
radioLastPaymentRule: {
block: true,
@ -100,11 +77,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 +88,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxLastPaymentPerc: {
min: 0,
@ -124,11 +95,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 +106,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxRedemptionPaymentSum: {
min: 1000,
@ -150,55 +115,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 +151,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
selectLeaseObjectType: {
showSearch: true,
@ -231,9 +172,6 @@ const props: Partial<ElementsProps> = {
min: 1,
max: 1000,
addonAfter: 'шт.',
style: {
width: '100%',
},
},
selectLeaseObjectUseFor: {
showSearch: true,
@ -242,9 +180,6 @@ const props: Partial<ElementsProps> = {
tbxLeaseObjectYear: {
min: 1994,
max: dayjs().year(),
style: {
width: '100%',
},
},
selectLeaseObjectCategory: {
showSearch: false,
@ -261,9 +196,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'л.с.',
style: {
width: '100%',
},
},
tbxEngineVolume: {
min: 0,
@ -271,11 +203,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 +213,12 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'кг',
style: {
width: '100%',
},
},
tbxCountSeats: {
min: 0,
max: 2000,
precision: 0,
parser,
style: {
width: '100%',
},
},
tbxMaxSpeed: {
min: 0,
@ -303,9 +226,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'км/ч',
style: {
width: '100%',
},
},
selectDealer: {
showSearch: true,
@ -316,54 +236,36 @@ 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%',
},
},
tbxInsFranchise: {
min: 0,
@ -373,23 +275,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 +296,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 +332,6 @@ const props: Partial<ElementsProps> = {
precision: 2,
parser,
formatter,
style: {
width: '100%',
},
},
selectLead: {
showSearch: true,
@ -479,42 +345,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 +382,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxVehicleTaxInYear: {
min: 0,
@ -535,9 +391,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxVehicleTaxInLeasingPeriod: {
min: 0,
@ -547,9 +400,6 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
selectObjectRegionRegistration: {
showSearch: true,
@ -562,9 +412,6 @@ const props: Partial<ElementsProps> = {
formatter,
readOnly: true,
controls: false,
style: {
width: '100%',
},
},
selectLegalClientRegion: {
showSearch: true,
@ -589,35 +436,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,6 +1,4 @@
import { IRRAddon } from '../../addons/irr-addon';
import { ProductAddon } from '../../addons/product-addon';
import { buildLink } from '../../builders';
import buildReadonly from '../../builders/build-readonly';
import components from '../elements-components';
import elementsProps from '../elements-props';
import titles from '../elements-titles';
@ -8,131 +6,20 @@ import types from '../elements-types';
import map from '../map';
import type { RenderProps } from './types';
import { Container, Head } from '@/Components/Layout/Element';
import { useErrors, useStore } from '@/stores/hooks';
import { useStore } from '@/stores/hooks';
import { useIsFetching } from '@tanstack/react-query';
import { observer } from 'mobx-react-lite';
import type { ComponentProps } from 'react';
import { Link, Tooltip } from 'ui/elements';
import { LoadingOutlined } from 'ui/elements/icons';
import Link from 'ui/elements/Link';
import Tooltip from 'ui/elements/Tooltip';
const defaultLinkProps: ComponentProps<typeof Link> = {
children: 'Открыть в CRM',
size: 'small',
style: { padding: 0 },
text: 'Открыть в CRM',
type: 'link',
};
const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
btnCalculate: {
render: () => {
const elementName = 'btnCalculate';
const title = titles.btnCalculate;
const valueName = map.btnCalculate;
const Component = components.btnCalculate;
const props = elementsProps.btnCalculate;
const { builder } = types.btnCalculate();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $process } = useStore();
const { hasErrors } = useErrors();
return (
<Container>
<Head htmlFor={elementName} title={title} />
<Element
{...props}
loading={$process.has('Calculate') || $process.has('CreateKP')}
disabled={
$process.has('LoadKP') ||
(!$process.has('Unlimited') && hasErrors) ||
$process.has('Tarif')
}
/>
</Container>
);
});
return <RenderedComponent />;
},
},
btnCreateKP: {
render: () => {
const elementName = 'btnCreateKP';
const title = titles.btnCreateKP;
const valueName = map.btnCreateKP;
const Component = components.btnCreateKP;
const props = elementsProps.btnCreateKP;
const { builder } = types.btnCreateKP();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $process } = useStore();
const { hasErrors } = useErrors();
return (
<Container>
<Head htmlFor={elementName} title={title} />
<Element
{...props}
loading={$process.has('Calculate') || $process.has('CreateKP')}
disabled={
$process.has('LoadKP') ||
(!$process.has('Unlimited') && hasErrors) ||
$process.has('Tarif')
}
/>
</Container>
);
});
return <RenderedComponent />;
},
},
btnCreateKPMini: {
render: () => {
const elementName = 'btnCreateKPMini';
const title = titles.btnCreateKPMini;
const valueName = map.btnCreateKPMini;
const Component = components.btnCreateKPMini;
const props = elementsProps.btnCreateKPMini;
const { builder } = types.btnCreateKPMini();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $process } = useStore();
const { hasErrors } = useErrors();
return (
<Container>
<Head htmlFor={elementName} title={title} />
<Element
{...props}
loading={$process.has('Calculate') || $process.has('CreateKP')}
disabled={$process.has('LoadKP') || (!$process.has('Unlimited') && hasErrors)}
/>
</Container>
);
});
return <RenderedComponent />;
},
},
selectHighSeasonStart: {
render: () => {
const elementName = 'selectHighSeasonStart';
@ -172,7 +59,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
valueName,
});
const LinkComponent = buildLink(Link, {
const LinkComponent = buildReadonly(Link, {
elementName: 'linkLeadUrl',
valueName: 'leadUrl',
});
@ -204,7 +91,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
valueName,
});
const LinkComponent = buildLink(Link, {
const LinkComponent = buildReadonly(Link, {
elementName: 'linkOpportunityUrl',
valueName: 'opportunityUrl',
});
@ -222,29 +109,6 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
},
},
selectProduct: {
render: () => {
const elementName = 'selectProduct';
const title = titles.selectProduct;
const valueName = map.selectProduct;
const Component = components.selectProduct;
const props = elementsProps.selectProduct;
const { builder } = types.selectProduct();
const Element = builder(Component, {
elementName,
valueName,
});
return (
<Container key={elementName}>
<Head addon={<ProductAddon />} htmlFor={elementName} title={title} />
<Element {...props} id={elementName} />
</Container>
);
},
},
selectQuote: {
render: () => {
const elementName = 'selectQuote';
@ -259,7 +123,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
valueName,
});
const LinkComponent = buildLink(Link, {
const LinkComponent = buildReadonly(Link, {
elementName: 'linkQuoteUrl',
valueName: 'quoteUrl',
});
@ -316,36 +180,6 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
},
},
tbxIRR_Perc: {
render: () => {
const elementName = 'tbxIRR_Perc';
const title = titles.tbxIRR_Perc;
const valueName = map.tbxIRR_Perc;
const Component = components.tbxIRR_Perc;
const props = elementsProps.tbxIRR_Perc;
const { builder } = types.tbxIRR_Perc();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $calculation } = useStore();
const { max, min } = $calculation.$values.getValue('irrInfo');
return (
<Container>
<Head htmlFor={elementName} title={title} addon={<IRRAddon />} />
<Element {...props} min={min > 0 ? min : undefined} max={max > 0 ? max : undefined} />
</Container>
);
});
return <RenderedComponent />;
},
},
tbxVehicleTaxInYear: {
render: () => {
const elementName = 'tbxVehicleTaxInYear';
@ -370,7 +204,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
<Element
{...props}
id={elementName}
addonBefore={isFetching ? <LoadingOutlined spin rev="" /> : null}
addonBefore={isFetching && <LoadingOutlined spin />}
/>
</Container>
</Tooltip>

View File

@ -1,3 +1,4 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
@ -93,7 +94,7 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectTarif: 'Тариф',
tbxCreditRate: 'Ставка привлечения, %',
selectRate: 'Ставка привлечения',
tbxMaxPriceChange: 'Макс. возможное изменение стоимости ПЛ',
tbxMaxPriceChange: 'Макс.возможное изменение стоимости ПЛ',
tbxImporterRewardPerc: 'АВ импортера, %',
tbxImporterRewardRub: 'АВ импортера, руб.',
cbxDisableChecks: 'Отключить все проверки',
@ -124,13 +125,6 @@ const titles: Record<ActionElements | ValuesElements, string> = {
tbxBonusCoefficient: 'Коэффициент снижения бонуса',
selectLeasingWithoutKasko: 'Лизинг без КАСКО',
tbxVIN: 'VIN',
selectUser: 'Пользователь',
cbxSupplierFinancing: 'Финансирование поставщика',
tbxPi: 'PI',
cbxPartialVAT: 'Частичный НДС',
cbxFloatingRate: 'Плавающая ставка',
cbxQuotePriceWithFullVAT: 'Отображать Стоимость ПЛ с полным НДС',
cbxQuoteShowAcceptLimit: 'Отображать одобренный лимит',
/** Link Elements */
linkDownloadKp: '',
@ -149,7 +143,6 @@ const titles: Record<ActionElements | ValuesElements, string> = {
/** Action Elements */
btnCalculate: '',
btnCreateKP: '',
btnCreateKPMini: '',
};
export default titles;

View File

@ -1,22 +1,11 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import * as b from '../builders';
import { buildAction, buildOptions, buildReadonly, buildValue } from '../builders';
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
type ElementTypes =
| 'Action'
| 'Check'
| 'Link'
| 'Number'
| 'Options'
| 'Readonly'
| 'Switch'
| 'Value';
type Builders =
| typeof b.buildAction
| typeof b.buildOptions
| typeof b.buildReadonly
| typeof b.buildValue;
type ElementTypes = 'Action' | 'Options' | 'Readonly' | 'Value';
type Builders = typeof buildAction | typeof buildOptions | typeof buildReadonly | typeof buildValue;
type GetType = () => {
builder: Builders;
@ -30,35 +19,19 @@ function wrapTypeGetters<G extends GetType, E extends Record<ElementTypes, G>>(a
const t = wrapTypeGetters({
Value: () => ({
typeName: 'Value',
builder: b.buildValue,
}),
Number: () => ({
typeName: 'Number',
builder: b.buildNumberValue,
builder: buildValue,
}),
Readonly: () => ({
typeName: 'Readonly',
builder: b.buildReadonly,
builder: buildReadonly,
}),
Options: () => ({
typeName: 'Options',
builder: b.buildOptions,
builder: buildOptions,
}),
Action: () => ({
typeName: 'Action',
builder: b.buildAction,
}),
Link: () => ({
typeName: 'Link',
builder: b.buildLink,
}),
Check: () => ({
typeName: 'Check',
builder: b.buildCheck,
}),
Switch: () => ({
typeName: 'Switch',
builder: b.buildSwitch,
builder: buildAction,
}),
});
@ -67,68 +40,68 @@ function wrapElementsTypes<T, E extends Record<ActionElements | ValuesElements,
}
const types = wrapElementsTypes({
cbxRecalcWithRevision: t.Check,
tbxLeaseObjectPrice: t.Number,
tbxLeaseObjectPriceWthtVAT: t.Number,
tbxVATInLeaseObjectPrice: t.Number,
tbxEngineHours: t.Number,
tbxSupplierDiscountRub: t.Number,
tbxSupplierDiscountPerc: t.Number,
tbxLeasingPeriod: t.Number,
tbxFirstPaymentPerc: t.Number,
tbxFirstPaymentRub: t.Number,
tbxLastPaymentPerc: t.Number,
tbxLastPaymentRub: t.Number,
cbxRecalcWithRevision: t.Value,
tbxLeaseObjectPrice: t.Value,
tbxLeaseObjectPriceWthtVAT: t.Value,
tbxVATInLeaseObjectPrice: t.Value,
tbxEngineHours: t.Value,
tbxSupplierDiscountRub: t.Value,
tbxSupplierDiscountPerc: t.Value,
tbxLeasingPeriod: t.Value,
tbxFirstPaymentPerc: t.Value,
tbxFirstPaymentRub: t.Value,
tbxLastPaymentPerc: t.Value,
tbxLastPaymentRub: t.Value,
selectImportProgram: t.Options,
tbxImportProgramSum: t.Readonly,
tbxAddEquipmentPrice: t.Number,
tbxRedemptionPaymentSum: t.Number,
tbxParmentsDecreasePercent: t.Number,
tbxComissionPerc: t.Number,
tbxComissionRub: t.Number,
tbxSaleBonus: t.Number,
tbxIRR_Perc: t.Number,
tbxLeaseObjectCount: t.Number,
cbxWithTrailer: t.Check,
cbxLeaseObjectUsed: t.Check,
tbxMaxMass: t.Number,
tbxCountSeats: t.Number,
tbxMaxSpeed: t.Number,
tbxLeaseObjectYear: t.Number,
tbxLeaseObjectMotorPower: t.Number,
tbxEngineVolume: t.Number,
tbxDealerRewardSumm: t.Number,
tbxDealerBrokerRewardSumm: t.Number,
tbxIndAgentRewardSumm: t.Number,
tbxCalcDoubleAgentRewardSumm: t.Number,
tbxCalcBrokerRewardSum: t.Number,
tbxFinDepartmentRewardSumm: t.Number,
cbxInsDecentral: t.Check,
tbxInsFranchise: t.Number,
cbxInsUnlimitDrivers: t.Switch,
tbxInsAgeDrivers: t.Number,
tbxInsExpDrivers: t.Number,
cbxLastPaymentRedemption: t.Switch,
cbxPriceWithDiscount: t.Switch,
cbxFullPriceWithDiscount: t.Switch,
cbxCostIncrease: t.Switch,
cbxInsurance: t.Switch,
cbxRegistrationQuote: t.Switch,
cbxTechnicalCardQuote: t.Switch,
cbxNSIB: t.Switch,
tbxAddEquipmentPrice: t.Value,
tbxRedemptionPaymentSum: t.Value,
tbxParmentsDecreasePercent: t.Value,
tbxComissionPerc: t.Value,
tbxComissionRub: t.Value,
tbxSaleBonus: t.Value,
tbxIRR_Perc: t.Value,
tbxLeaseObjectCount: t.Value,
cbxWithTrailer: t.Value,
cbxLeaseObjectUsed: t.Value,
tbxMaxMass: t.Value,
tbxCountSeats: t.Value,
tbxMaxSpeed: t.Value,
tbxLeaseObjectYear: t.Value,
tbxLeaseObjectMotorPower: t.Value,
tbxEngineVolume: t.Value,
tbxDealerRewardSumm: t.Value,
tbxDealerBrokerRewardSumm: t.Value,
tbxIndAgentRewardSumm: t.Value,
tbxCalcDoubleAgentRewardSumm: t.Value,
tbxCalcBrokerRewardSum: t.Value,
tbxFinDepartmentRewardSumm: t.Value,
cbxInsDecentral: t.Value,
tbxInsFranchise: t.Value,
cbxInsUnlimitDrivers: t.Value,
tbxInsAgeDrivers: t.Value,
tbxInsExpDrivers: t.Value,
cbxLastPaymentRedemption: t.Value,
cbxPriceWithDiscount: t.Value,
cbxFullPriceWithDiscount: t.Value,
cbxCostIncrease: t.Value,
cbxInsurance: t.Value,
cbxRegistrationQuote: t.Value,
cbxTechnicalCardQuote: t.Value,
cbxNSIB: t.Value,
tbxQuoteName: t.Value,
cbxQuoteRedemptionGraph: t.Switch,
cbxShowFinGAP: t.Switch,
tbxCreditRate: t.Number,
tbxMaxPriceChange: t.Number,
tbxImporterRewardPerc: t.Number,
tbxImporterRewardRub: t.Number,
cbxDisableChecks: t.Switch,
tbxMileage: t.Number,
tbxTotalPayments: t.Number,
tbxVehicleTaxInYear: t.Number,
tbxVehicleTaxInLeasingPeriod: t.Number,
tbxMinPriceChange: t.Number,
cbxQuoteRedemptionGraph: t.Value,
cbxShowFinGAP: t.Value,
tbxCreditRate: t.Value,
tbxMaxPriceChange: t.Value,
tbxImporterRewardPerc: t.Value,
tbxImporterRewardRub: t.Value,
cbxDisableChecks: t.Value,
tbxMileage: t.Value,
tbxTotalPayments: t.Value,
tbxVehicleTaxInYear: t.Value,
tbxVehicleTaxInLeasingPeriod: t.Value,
tbxMinPriceChange: t.Value,
selectProduct: t.Options,
selectClientRisk: t.Options,
@ -189,13 +162,6 @@ const types = wrapElementsTypes({
tbxBonusCoefficient: t.Value,
selectLeasingWithoutKasko: t.Options,
tbxVIN: t.Value,
selectUser: t.Options,
cbxSupplierFinancing: t.Switch,
tbxPi: t.Number,
cbxPartialVAT: t.Switch,
cbxFloatingRate: t.Switch,
cbxQuotePriceWithFullVAT: t.Switch,
cbxQuoteShowAcceptLimit: t.Switch,
labelLeaseObjectRisk: t.Readonly,
tbxInsKaskoPriceLeasePeriod: t.Readonly,
@ -205,13 +171,12 @@ const types = wrapElementsTypes({
tbxSubsidySum: t.Readonly,
btnCreateKP: t.Action,
btnCreateKPMini: t.Action,
btnCalculate: t.Action,
linkDownloadKp: t.Link,
linkLeadUrl: t.Link,
linkOpportunityUrl: t.Link,
linkQuoteUrl: t.Link,
linkDownloadKp: t.Readonly,
linkLeadUrl: t.Readonly,
linkOpportunityUrl: t.Readonly,
linkQuoteUrl: t.Readonly,
});
export default types;

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;

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