Compare commits

..

16 Commits

Author SHA1 Message Date
Chika
eea7109fe6 next.config.js: use rewrites only for development 2022-07-17 10:27:13 +03:00
Chika
ab0670ddf8 html: add htmlfor for elements 2022-07-17 10:22:11 +03:00
Chika
e6f6639366 css: add fonts to project 2022-07-17 09:36:06 +03:00
Chika
89c6e47a85 css: use normalize npm package 2022-07-17 09:34:20 +03:00
Chika
458307508b http: speed up loading page 2022-07-16 22:20:40 +03:00
Chika
72e0cbb0f3 graphql: fix apollo http errors on client 2022-07-16 20:27:44 +03:00
Chika
ed5f096afa authorization: fix auth url is undefined 2022-07-16 19:12:43 +03:00
Chika
b9d9c9d7bc apollo: fix access graphql url on server 2022-07-16 18:13:06 +03:00
Chika
a780656f29 process/payments: run validation immediately 2022-07-16 17:53:53 +03:00
Chika
090217039e page/index: fix data is undefined 2022-07-16 15:28:33 +03:00
Chika
e607176770 msw: revert auth url 2022-07-16 15:24:26 +03:00
Chika
6d9a26a54d env: move URL_GET_USER to env.local 2022-07-16 15:12:21 +03:00
Chika
a0cc238508 build: not check types/graphql.ts 2022-07-16 14:19:09 +03:00
Chika
225c0e5a7b Dockerfile: add new ARGs 2022-07-16 14:12:59 +03:00
Chika
e0fc9077b4 remove .env files 2022-07-16 11:56:16 +03:00
Chika
fcf521eb8c build: add Dockerfile & changes 2022-07-16 11:48:13 +03:00
622 changed files with 15191 additions and 54574 deletions

View File

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

2
.env Normal file
View File

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

View File

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

View File

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

78
.eslintrc.json Normal file
View File

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

51
.gitignore vendored
View File

@ -1,44 +1,19 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Created by https://www.toptal.com/developers/gitignore/api/nextjs
# Edit at https://www.toptal.com/developers/gitignore?templates=nextjs
### NextJS ###
# dependencies
node_modules
.pnp
/node_modules
/.pnp
.pnp.js
# testing
coverage
/coverage
# next.js
.next/
out/
/.next/
/out/
# production
build
/build
# misc
.DS_Store
@ -58,15 +33,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
# End of https://www.toptal.com/developers/gitignore/api/nextjs
# Created by https://www.toptal.com/developers/gitignore/api/turbo
# Edit at https://www.toptal.com/developers/gitignore?templates=turbo
### Turbo ###
# Turborepo task cache
.turbo
# End of https://www.toptal.com/developers/gitignore/api/turbo
.pnpm

2
.graphqlrc.yml Normal file
View File

@ -0,0 +1,2 @@
schema: './graphql/crm.schema.graphql'
documents: '**/*.{graphql,js,ts,jsx,tsx}'

View File

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

View File

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

View File

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

10
.vscode/launch.json vendored
View File

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

18
.vscode/settings.json vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,36 @@
import Table from 'Elements/Table';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import { columns } from './config';
const FinGAPTable = observer(() => {
const { $tables } = useStore();
const { fingap } = $tables;
return (
<Table
columns={columns}
dataSource={fingap.risks}
rowSelection={{
type: 'checkbox',
onChange: (_, selectedRows) => {
const selectedKeys = selectedRows.reduce((acc, row) => {
acc.push(row.key);
if (row.keys) return [...acc, ...row.keys];
return acc;
}, []);
fingap.setSelectedKeys(selectedKeys);
},
}}
pagination={false}
size="small"
scroll={{
x: true,
}}
/>
);
});
export default FinGAPTable;

View File

@ -0,0 +1,10 @@
export type Risk = {
key: string;
riskId: string;
riskName: string;
calcType: number;
premiumPerc: number;
sum: number;
premium: number;
keys?: string[];
};

View File

@ -1,8 +1,8 @@
import { useInsuranceValue } from './hooks';
import type { Values } from './types';
import { useRow } from '@/stores/tables/insurance/hooks';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useRowOptions, useRowStatuses } from 'stores/tables/insurance/hooks';
import { useInsuranceValue } from './hooks';
import type { Values } from './types';
export function buildOptionComponent<T>(
key: string,
@ -11,17 +11,15 @@ export function buildOptionComponent<T>(
) {
return observer((props: T) => {
const [value, setValue] = useInsuranceValue(key, valueName);
const { getOptions, getStatus } = useRow(key);
const options = getOptions(valueName);
const status = getStatus(valueName);
const options = useRowOptions(key);
const statuses = useRowStatuses(key);
return (
<Component
options={options}
onChange={setValue}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
value={value}
options={options[valueName]}
setValue={setValue}
status={statuses[valueName]}
{...props}
/>
);
@ -35,11 +33,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 = useRowStatuses(key);
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
return <Component value={value} setValue={setValue} status={statuses[valueName]} {...props} />;
});
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
import type { BaseOption, Status } from 'Elements/types';
export type Keys = 'osago' | 'kasko' | 'fingap';
export type RowValues = {
key: Keys;
policyType: string;
insuranceCompany: string | null;
insured: 100_000_000 | 100_000_001 | null;
insCost: number;
insTerm: 100_000_000 | 100_000_001 | null;
};
export type Values = Exclude<keyof RowValues, 'key'>;
export type RowOptions = {
[ValueName in Values]?: BaseOption<RowValues[ValueName]>[];
};
export type RowStatuses = Record<Values, Status>;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ export const id = 'payments';
export const title = 'Платежи';
export const elements: ElementsRow['0'] = [
'radioGraphType',
'selectSeasonType',
'tbxParmentsDecreasePercent',
'selectHighSeasonStart',

View File

@ -0,0 +1,42 @@
import { Box, Flex } from 'UIKit/grid';
import elementsRender from '../../config/elements-render';
import { elements, id, title } from './config';
import PaymentsTable from './PaymentsTable';
function Payments() {
const renderedElements = elements.map((elementName) => {
const render = elementsRender[elementName]?.render;
return render();
});
// eslint-disable-next-line operator-linebreak
const [radioGraphType, selectSeasonType, tbxParmentsDecreasePercent, selectHighSeasonStart] =
renderedElements;
return (
<Flex flexDirection="column">
<Box
sx={{
display: 'grid',
gridTemplateColumns: ['1fr', '1fr', '1fr 1fr'],
gap: '10px',
}}
>
{radioGraphType}
<Flex flexDirection="column">
{selectSeasonType}
{tbxParmentsDecreasePercent}
{selectHighSeasonStart}
</Flex>
</Box>
<PaymentsTable />
</Flex>
);
}
export default {
id,
title,
Component: Payments,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,21 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: GetCurrencySymbol
// ====================================================
export interface GetCurrencySymbol_transactioncurrency {
__typename: "transactioncurrency";
currencysymbol: string | null;
}
export interface GetCurrencySymbol {
transactioncurrency: GetCurrencySymbol_transactioncurrency | null;
}
export interface GetCurrencySymbolVariables {
currencyid: any;
}

View File

@ -0,0 +1,43 @@
import { gql, useApolloClient } from '@apollo/client';
import { observer } from 'mobx-react-lite';
import { useState } from 'react';
import { useStore } from 'stores/hooks';
// prettier-ignore
import type { GetCurrencySymbol, GetCurrencySymbolVariables } from './__generated__/GetCurrencySymbol';
const QUERY_GET_CURRENCY_SYMBOL = gql`
query GetCurrencySymbol($currencyid: Uuid!) {
transactioncurrency(transactioncurrencyid: $currencyid) {
currencysymbol
}
}
`;
const CurrencyAddon = observer(() => {
const { $calculation } = useStore();
const currencyid = $calculation.$values.getValue('supplierCurrency');
const [currencySymbol, setCurrencySymbol] = useState<string | null | undefined>(null);
const { query } = useApolloClient();
if (!currencyid) return null;
query<GetCurrencySymbol, GetCurrencySymbolVariables>({
query: QUERY_GET_CURRENCY_SYMBOL,
variables: {
currencyid,
},
})
.then(({ data }) => {
setCurrencySymbol(data?.transactioncurrency?.currencysymbol);
})
.catch(() => {
setCurrencySymbol(null);
});
return <span>{currencySymbol}</span>;
});
export default <CurrencyAddon />;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,15 @@
/* eslint-disable react/no-array-index-key */
import Divider from 'Elements/layout/Divider';
import type { BoxProps } from 'UIKit/grid';
import { Box } from 'UIKit/grid';
import elementsRender from '../config/elements-render';
import type { Elements as ActionElements } from '../config/map/actions';
import type { Elements as ValuesElements } from '../config/map/values';
import { Divider } from 'ui/elements';
import type { BoxProps } from 'ui/grid';
import { Box } from 'ui/grid';
export type ElementsRow = [
elements: Array<ActionElements | ValuesElements>,
style?: BoxProps['sx']
];
export type ElementsRow = [elements: (ValuesElements | ActionElements)[], style?: BoxProps['sx']];
type DividerRow = { title: string };
export type FormTabRows = Array<DividerRow | ElementsRow>;
export type FormTabRows = Array<ElementsRow | DividerRow>;
function renderFormRows(rowsConfig: FormTabRows) {
const rows = rowsConfig.map((row, i) => {
@ -21,6 +18,7 @@ function renderFormRows(rowsConfig: FormTabRows) {
const renderedElements = elements.map((elementName) => {
const render = elementsRender[elementName]?.render;
// eslint-disable-next-line object-curly-newline
return render({});
});
@ -29,8 +27,8 @@ function renderFormRows(rowsConfig: FormTabRows) {
key={i.toString()}
sx={{
display: 'grid',
gap: '10px',
gridTemplateColumns: ['1fr', '1fr', 'repeat(3, 1fr)'],
gap: '10px',
...style,
}}
>

View File

@ -0,0 +1,56 @@
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
import { Flex } from 'UIKit/grid';
import { min } from 'UIKit/mq';
const UserText = styled.span`
margin: 0;
padding: 0;
text-transform: uppercase;
color: #fff;
font-size: 0.5rem;
font-family: 'Montserrat', sans-serif;
font-weight: 500;
${min('laptop')} {
font-size: 0.75rem;
}
`;
const User = observer(() => {
const { $user } = useStore();
return <UserText>{$user?.user?.displayName}</UserText>;
});
const Logout = styled.a`
margin: 0;
padding: 0;
text-transform: uppercase;
color: #fff;
font-size: 0.45rem;
font-family: 'Montserrat', sans-serif;
font-weight: 500;
${min('laptop')} {
font-size: 0.55rem;
}
`;
function Auth() {
return (
<Flex
flexDirection="column"
alignItems="flex-end"
alignSelf={['flex-start']}
justifyContent="space-between"
height="100%"
>
<User />
<Logout href="/logout">Выход</Logout>
</Flex>
);
}
export default Auth;

View File

@ -1,8 +1,7 @@
/* eslint-disable func-style */
import { min } from '@/styles/mq';
import type { ReactNode } from 'react';
import styled from 'styled-components';
import { Flex } from 'ui/grid';
import { Flex } from 'UIKit/grid';
import { min } from 'UIKit/mq';
const ElementTitle = styled.label`
color: rgba(0, 0, 0, 0.75);
@ -24,12 +23,12 @@ export function Head({
addon,
htmlFor,
}: {
title: string;
addon?: ReactNode;
htmlFor: string;
title: ReactNode;
}) {
return (
<Flex alignItems={['center']} flexDirection={['row']} justifyContent={['space-between']}>
<Flex flexDirection={['row']} justifyContent={['space-between']} alignItems={['center']}>
<ElementTitle htmlFor={htmlFor}>{title}</ElementTitle>
{addon}
</Flex>

View File

@ -1,13 +1,13 @@
/* eslint-disable import/no-unresolved */
import styled from 'styled-components';
import { Flex } from 'UIKit/grid';
import { min } from 'UIKit/mq';
import Auth from './Auth';
import Logo from './Logo';
import { min } from '@/styles/mq';
import styled from 'styled-components';
import { Flex } from 'ui/grid';
const HeaderContent = styled(Flex)`
flex-direction: row;
justify-content: space-between;
padding: 14px 12px;
background: linear-gradient(
90deg,
@ -16,7 +16,10 @@ const HeaderContent = styled(Flex)`
var(--color-tertiarty) 100%
);
${'' /* height: 70px; */}
padding: 10px 12px;
${min('laptop')} {
padding: 10px 12px;
padding-left: 20px;
}
`;

View File

@ -0,0 +1,37 @@
import Image from 'next/image';
import logo from 'public/assets/images/logo-long.png';
import styled from 'styled-components';
import { Flex } from 'UIKit/grid';
import { min } from 'UIKit/mq';
const ImageWrapper = styled.div`
width: 100px;
${min('laptop')} {
width: 135px;
}
`;
const LogoText = styled.h3`
margin: 0;
text-transform: uppercase;
color: #fff;
font-size: 0.85rem;
font-family: 'Montserrat', sans-serif;
font-weight: 500;
${min('laptop')} {
font-size: 1.2rem;
}
`;
function Logo() {
return (
<Flex flexDirection="column" alignItems="flex-start" justifyContent="space-between">
<ImageWrapper>
<Image alt="logo" src={logo} layout="responsive" objectFit="contain" />
</ImageWrapper>
<LogoText>Лизинговый Калькулятор</LogoText>
</Flex>
);
}
export default Logo;

View File

@ -0,0 +1,13 @@
/* eslint-disable react/prop-types */
/* eslint-disable import/no-unresolved */
import { Flex } from 'UIKit/grid';
import Header from './Header';
export default function Layout({ children }) {
return (
<Flex flexDirection="column">
<Header />
<main>{children}</main>
</Flex>
);
}

View File

@ -0,0 +1,40 @@
/* eslint-disable import/prefer-default-export */
import type { ColumnsType } from 'antd/lib/table';
import type { Payment } from './types';
export const columns: ColumnsType<Payment> = [
{
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,
},
];

View File

@ -0,0 +1,32 @@
import { MAX_LEASING_PERIOD } from 'constants/values';
import Table from 'Elements/Table';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import { columns } from './config';
const PaymentsTable = observer(() => {
const { $results } = useStore();
return (
<Table
columns={columns}
dataSource={toJS($results.payments)}
size="small"
pagination={{
defaultPageSize: 12,
pageSizeOptions: [12, MAX_LEASING_PERIOD],
responsive: true,
}}
scroll={{
x: true,
}}
/>
);
});
export default {
id: 'payments-table',
title: 'Таблица платежей',
Component: PaymentsTable,
};

View File

@ -0,0 +1,7 @@
export type Payment = {
key: string;
num: number;
paymentSum: string;
ndsCompensation: string;
redemptionAmount: string;
};

View File

@ -0,0 +1,76 @@
/* eslint-disable object-curly-newline */
import type { Values } from 'stores/results/types';
export const id = 'output';
export const title = 'Результаты';
export const titles: Record<Values, string> = {
resultTotalGraphwithNDS: 'Итого по графику, с НДС',
resultPlPrice: 'Стоимость ПЛ с НДС',
resultPriceUpPr: 'Удорожание, год',
resultIRRGraphPerc: 'IRR по графику клиента, %',
resultIRRNominalPerc: 'IRR (номинал), %',
resultInsKasko: 'КАСКО, НС, ДГО в графике',
resultInsOsago: 'ОСАГО в графике',
resultDopProdSum: 'Общая сумма доп.продуктов',
resultFirstPayment: 'Первый платеж',
resultLastPayment: 'Последний платеж',
resultTerm: 'Срок, мес.',
resultAB_FL: 'АВ ФЛ, без НДФЛ.',
resultAB_UL: 'АВ ЮЛ, с НДС.',
resultBonusMPL: 'Бонус МПЛ за лизинг, без НДФЛ',
resultDopMPLLeasing: 'Доп.бонус МПЛ за лизинг, без НДФЛ',
resultBonusDopProd: 'Бонус МПЛ за доп.продукты, без НДФЛ',
resultBonusSafeFinance: 'Бонус за Safe Finance без НДФЛ',
resultFirstPaymentRiskPolicy: 'Первый платеж по риск политике, %',
};
const moneyFormatters = Object.fromEntries(
(
[
'resultTotalGraphwithNDS',
'resultPlPrice',
'resultInsKasko',
'resultInsOsago',
'resultDopProdSum',
'resultFirstPayment',
'resultLastPayment',
'resultAB_FL',
'resultAB_UL',
'resultBonusMPL',
'resultDopMPLLeasing',
'resultBonusDopProd',
'tbxSubsidySum',
'resultBonusSafeFinance',
'resultPriceUpPr',
] as Values[]
).map((a) => [
a,
// prettier-ignore
Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
])
);
const percentFormatters = Object.fromEntries(
(['resultIRRGraphPerc', 'resultIRRNominalPerc', 'resultFirstPaymentRiskPolicy'] as Values[]).map(
(a) => [
a,
// prettier-ignore
Intl.NumberFormat('ru', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format,
]
)
);
const defaultFormatters = {
resultTerm: Intl.NumberFormat('ru').format,
};
export const formatters = Object.assign(moneyFormatters, percentFormatters, defaultFormatters);

View File

@ -0,0 +1,46 @@
import { Container, Head } from 'Components/Layout/Element';
import Text from 'Elements/Text';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
import { Box } from 'UIKit/grid';
import { min } from 'UIKit/mq';
import { formatters, id, title, titles } from './config';
const Grid = styled(Box)`
display: grid;
grid-template-columns: 1fr;
${min('tablet')} {
grid-template-columns: 1fr 1fr;
}
`;
const Results = observer(() => {
const { $results } = useStore();
const values = toJS($results.values);
return (
<Grid>
{Object.keys(values).map((valueName) => {
const formatter = formatters[valueName];
const storeValue = values[valueName];
const value = formatter(storeValue);
return (
<Container key={valueName}>
<Head title={titles[valueName]} />
<Text>{value}</Text>
</Container>
);
})}
</Grid>
);
});
export default {
id,
title,
Component: Results,
};

View File

@ -0,0 +1,77 @@
import Alert from 'Elements/Alert';
import { observer } from 'mobx-react-lite';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
import { Box, Flex } from 'UIKit/grid';
import titles from '../Calculation/config/elements-titles';
const Bold = styled.span`
font-weight: bold;
`;
function Message(title, text) {
return (
<>
<Bold>{title}</Bold>
{': '}
{text}
</>
);
}
function getElementsErrors($calculation) {
const { elementsErrors } = $calculation.$validation;
const errors = Object.keys(elementsErrors).map((elementName) => {
const elementErrors = elementsErrors[elementName];
const elementTitle = titles[elementName];
return elementErrors.map((error) => (
<Alert key={error.name} type="error" showIcon message={Message(elementTitle, error.text)} />
));
});
return errors;
}
function getPaymentsTableErrors($tables) {
const { payments } = $tables;
const messages = payments.validation.getMessages();
const title = payments.validation.params.err_title;
return messages.map((text) => <Alert type="error" showIcon message={Message(title, text)} />);
}
function getInsuranceTableErrors($tables) {
const { insurance } = $tables;
const messages = insurance.validation.getMessages();
return messages.map((message) => <Alert type="error" showIcon message={message} />);
}
const Errors = observer(() => {
const { $calculation, $tables } = useStore();
const elementsErrors = getElementsErrors($calculation);
const paymentsErrors = getPaymentsTableErrors($tables);
const insuranceErrors = getInsuranceTableErrors($tables);
const errors = [...elementsErrors, ...paymentsErrors, ...insuranceErrors];
if (errors.length === 0) return <Alert type="success" showIcon message="Ошибок нет 🙂" />;
return <Flex flexDirection="column">{errors}</Flex>;
});
function Validation() {
return (
<Box>
<Errors />
</Box>
);
}
export default {
id: 'validation',
title: 'Ошибки',
Component: Validation,
};

View File

@ -0,0 +1,35 @@
import Background from 'Elements/layout/Background';
import Tabs from 'Elements/layout/Tabs';
import styled from 'styled-components';
import { min } from 'UIKit/mq';
import PaymentsTable from './PaymentsTable';
import Results from './Results';
import Validation from './Validation';
const outputTabs = [PaymentsTable, Results, Validation];
const Wrapper = styled(Background)`
padding: 4px 10px;
min-height: 200px;
${min('laptop')} {
padding: 4px 18px;
min-height: 641px;
}
`;
function Output() {
return (
<Wrapper>
<Tabs>
{outputTabs.map(({ id, title, Component }) => (
<Tabs.TabPane tab={title} key={id}>
<Component />
</Tabs.TabPane>
))}
</Tabs>
</Wrapper>
);
}
export default Output;

65
Dockerfile Normal file
View File

@ -0,0 +1,65 @@
# Install dependencies only when needed
FROM node:16-alpine AS deps
# 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
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# If using npm with a `package-lock.json` comment out above and use below instead
# COPY package.json package-lock.json ./
# RUN npm ci
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY
ARG NEXT_PUBLIC_COLOR_PRIMARY
ARG NEXT_PUBLIC_COLOR_SECONDARY
ARG NEXT_PUBLIC_COLOR_TERTIARTY
ARG NEXT_PUBLIC_FAVICON
ARG NEXT_TELEMETRY_DISABLED
ARG NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT
ARG NEXT_PUBLIC_URL_GET_USER
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]

3
Elements/Alert.js Normal file
View File

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

34
Elements/Button.tsx Normal file
View File

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

40
Elements/Checkbox.tsx Normal file
View File

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

25
Elements/Input.tsx Normal file
View File

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

29
Elements/InputNumber.tsx Normal file
View File

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

30
Elements/Link.tsx Normal file
View File

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

49
Elements/Radio.tsx Normal file
View File

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

32
Elements/Segmented.tsx Normal file
View File

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

46
Elements/Select.tsx Normal file
View File

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

21
Elements/Switch.tsx Normal file
View File

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

4
Elements/Table.js Normal file
View File

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

16
Elements/Text.tsx Normal file
View File

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

3
Elements/Tooltip.js Normal file
View File

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

View File

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

View File

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

View File

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

4
Elements/layout/Tabs.js Normal file
View File

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

8
Elements/notification.ts Normal file
View File

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

14
Elements/types.ts Normal file
View File

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

View File

@ -1,81 +1,34 @@
# Turborepo starter
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
This is an official starter Turborepo.
## Getting Started
## Using this example
First, run the development server:
Run the following command:
```sh
npx create-turbo@latest
```bash
npm run dev
# or
yarn dev
```
## What's inside?
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
This Turborepo includes the following packages/apps:
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
### Apps and Packages
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
- `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
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
## Learn More
### Utilities
To learn more about Next.js, take a look at the following resources:
This Turborepo has some additional tools already setup for you:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
- [TypeScript](https://www.typescriptlang.org/) for static type checking
- [ESLint](https://eslint.org/) for code linting
- [Prettier](https://prettier.io) for code formatting
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
### Build
## Deploy on Vercel
To build all apps and packages, run the following command:
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
```
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)
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

11
UIKit/colors.js Normal file
View File

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

View File

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

20
UIKit/mq.ts Normal file
View File

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

7
UIKit/theme.js Normal file
View File

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

12
apollo.config.js Normal file
View File

@ -0,0 +1,12 @@
/** @type {import('apollo').ApolloConfig} */
module.exports = {
client: {
service: {
name: 'crmgraphql',
// url: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT,
localSchemaFile: './graphql/crm.schema.graphql',
},
excludes: ['graphql/**/*'],
includes: ['pages/**/*', 'process/**/*', 'Components/**/*'],
},
};

35
apollo/client.js Normal file
View File

@ -0,0 +1,35 @@
/* eslint-disable no-underscore-dangle */
/* eslint-disable @typescript-eslint/naming-convention */
import { ApolloClient, InMemoryCache } from '@apollo/client';
/** @type {import('@apollo/client').ApolloClient<NormalizedCacheObject>} */
let apolloClient;
// prettier-ignore
const uri = typeof window === 'undefined'
? process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT
: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY;
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
uri,
cache: new InMemoryCache(),
});
}
export default function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}

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