Compare commits

...

236 Commits

Author SHA1 Message Date
vchikalkin
3c25ba30d0 merge branch 'release/dyn-4885' 2024-12-10 10:11:43 +03:00
vchikalkin
ba36eb502b merge branch 'release/dyn-4872' 2024-12-03 12:09:07 +03:00
vchikalkin
2c9a2bd30c fix selectDealerPerson validation 2024-10-18 16:29:07 +03:00
vchikalkin
f35acee64b fix tbxVATInLeaseObjectPrice block/unblock by cbxPartialVAT 2024-10-18 14:56:50 +03:00
vchikalkin
a4c02a747f merge branch 'release/dyn-4790' 2024-10-18 13:39:03 +03:00
vchikalkin
66b1e2cdcb merge branch 'fix/irr-bonus-reaction' 2024-09-23 09:59:51 +03:00
vchikalkin
81eca1c6e0 merge branch 'release/dyn-4762' 2024-09-19 10:12:18 +03:00
vchikalkin
89f2b3b68c merge branch 'release/dyn-4760' 2024-09-19 10:11:30 +03:00
vchikalkin
549bd901da partial fix release/4710 2024-09-12 16:50:38 +03:00
vchikalkin
6aad2d3aa2 apps/web: countSeats default value = 4 2024-09-12 16:25:04 +03:00
vchikalkin
09959e3ac3 apps/web: graphql: GetInsuranceCompanies -> sort совком first 2024-09-09 16:00:20 +03:00
vchikalkin
7014462d42 merge branch 'release/dyn-4744' 2024-09-09 15:39:50 +03:00
vchikalkin
62417b88ba merge branch release/dyn-4710 2024-09-05 17:36:38 +03:00
vchikalkin
bf0b5c9907 apps/web: sort evo_planpayments by evo_plandate 2024-08-20 17:42:24 +03:00
vchikalkin
5347c3ef6c apps/web: add check min bonus 2024-08-20 15:52:21 +03:00
vchikalkin
194292c083 apps/web: fix elt kasko list filter 2024-08-19 10:23:14 +03:00
vchikalkin
7d023dba1e apps/web: change elt/kasko error text 2024-08-19 10:03:13 +03:00
vchikalkin
9c63cadf29 merge branch release/dyn-4497 2024-08-15 11:54:28 +03:00
vchikalkin
88b7501b4d apps/web: pass device info to create-kp 2024-08-01 13:06:33 +03:00
vchikalkin
8f53187a3c apps/web: elt kasko fix outsideRoads 2024-07-23 11:09:05 +03:00
vchikalkin
ef8059b3b1 Reapply "web: process/add-product: disable selectInsNSIB reaction"
This reverts commit 0eccd799c79840fe8aa93461880b6ae61a217857.
2024-07-22 16:38:22 +03:00
vchikalkin
8627233a17 favicon: change icons for android & iphone 2024-07-19 13:48:51 +03:00
vchikalkin
2ece25dcb6 apps/web: changes site.webmanifest & add favicon.ico to <head> 2024-07-18 23:08:29 +03:00
vchikalkin
0eccd799c7 Revert "web: process/add-product: disable selectInsNSIB reaction"
This reverts commit 671b25a087ac9dc32bd25c209a7d5797d58d8bcd.
2024-07-18 11:37:29 +03:00
vchikalkin
2e8c333374 web\process\configurator\lib\helper: fix getRates 2024-07-18 11:33:32 +03:00
vchikalkin
671b25a087 web: process/add-product: disable selectInsNSIB reaction 2024-07-16 10:13:15 +03:00
vchikalkin
4a314326fb packages: upgrade next@14.2.5 2024-07-15 17:44:44 +03:00
vchikalkin
d0270a3c9e web: add service worker for pwa install 2024-07-15 15:08:11 +03:00
vchikalkin
71fc44e7a6 Dockerfile: specify pnpm version 2024-07-15 11:07:39 +03:00
vchikalkin
d6f5c73b9d merge branch dyn-4250_migrate-to-new-graphql 2024-07-15 11:04:47 +03:00
vchikalkin
d7908bb5da merge branch release/dyn-4515 2024-07-09 11:16:30 +03:00
vchikalkin
ed5391076c merge branch release/dyn-4514 2024-07-05 11:08:17 +03:00
vchikalkin
4a190e962a fix build: specify turbo version 2024-06-21 16:10:28 +03:00
vchikalkin
4644dbb404 merge branch feature/clear-cache-button 2024-06-21 16:05:49 +03:00
vchikalkin
5c6b419452 merge branch release/dyn-4312_recalc-after-kk 2024-05-22 09:31:51 +03:00
vchikalkin
048d132da2 apps/web: increase proxyTimeout up to 90s 2024-05-13 12:24:47 +03:00
vchikalkin
dd154a3004 merge branch fix/dyn-4368_credit-rate 2024-05-04 11:08:59 +03:00
vchikalkin
8dbcf4bb7f build: fix antd generate css for production 2024-04-24 15:23:13 +03:00
vchikalkin
c0f825a587 merge branch release/dyn-4331_fingap-rat 2024-04-20 10:26:00 +03:00
vchikalkin
2b3dd5e7b9 merge branch release/dyn-4284_dangerous-goods 2024-04-15 10:18:13 +03:00
vchikalkin
5d1aa99d75 apps/web: globals.css: revert text-decoration: none for 'a' tag 2024-04-11 19:55:48 +03:00
vchikalkin
4be47fced5 apps/web: replace normalize.css -> modern-normalize 2024-04-11 16:32:30 +03:00
vchikalkin
66fc8ee70e merge branch release/dyn-4114_filter-models 2024-04-11 15:00:34 +03:00
vchikalkin
09fc6956d8 apps/web (Admin/Cache): temp remove ReloadButton 2024-04-11 14:58:36 +03:00
vchikalkin
501ed13f8a scripts: add antd generate css script 2024-04-11 14:09:29 +03:00
vchikalkin
61132928b5 apps/web: beautify Loading spinner 2024-04-11 11:08:15 +03:00
vchikalkin
21b009c833 apps/web: add loading spinner between pages 2024-04-11 10:47:24 +03:00
vchikalkin
c616b981f1 apps/web: disable Link prefetch 2024-04-11 09:39:30 +03:00
vchikalkin
93d16cf35c merge branch feature/cache-manager-admin 2024-04-11 00:53:31 +03:00
vchikalkin
bd7cf3284b merge branch release/dyn-3905_elt-osago 2024-04-08 21:09:28 +03:00
vchikalkin
a0e6c4ff2f apps/web: fix get fingap from kp 2024-03-24 11:43:48 +03:00
vchikalkin
88fbb616b5 apps/web: fix ELT Kasko sum check 2024-03-24 10:59:02 +03:00
vchikalkin
7efea2acc9 merge branch fix/ui-ux-2203 2024-03-24 10:56:29 +03:00
vchikalkin
907f288815 DYN-4145: apps/web: корректируем КПП и Адреса в методе OSAGO 2024-03-18 17:55:40 +03:00
vchikalkin
2fea400060 apps/api: do not cache graphql responses with errors 2024-03-18 17:19:54 +03:00
vchikalkin
f695835c61 apps/web: process/used-pl: add ( ) symbols to vin regex 2024-03-07 13:39:13 +03:00
vchikalkin
47d47a0bfd apps/web: create Content component to separate from error 2024-03-04 16:41:22 +03:00
vchikalkin
540d5642a1 apps/web: fix show statusCode error 2024-03-04 16:26:54 +03:00
vchikalkin
2ec2a14706 merge branch release/dyn-3987_early-redemption-sums 2024-03-04 14:23:09 +03:00
vchikalkin
f7110497d3 apps/web: show apollo error for user 2024-03-01 11:28:36 +03:00
vchikalkin
c9326697f6 axios: capture more error information to sentry 2024-03-01 10:29:06 +03:00
vchikalkin
c2982cf31b apps/web: fix apollo modifyDataLink: check values are exist 2024-02-29 17:03:34 +03:00
vchikalkin
8c69b3b9c4 apps/web: add extra data for sentry 2024-02-29 16:56:43 +03:00
vchikalkin
24987be78e apps/web: fix telematic/tracker reaction 2024-02-29 16:06:15 +03:00
vchikalkin
defc625324 apps/api: check graphql response status & forward to response 2024-02-29 16:01:23 +03:00
vchikalkin
eee91d5f2d build: actualize .dockerignore 2024-02-29 15:17:10 +03:00
vchikalkin
a65202c653 apps/web: fix tbxVehicleTaxInYear addonBefore 2024-02-28 16:58:47 +03:00
vchikalkin
ffaad142e8 Docker: apps/api: fix build 2024-02-28 16:38:44 +03:00
vchikalkin
9e40e5141c merge branch feature/migrate-yarn-to-pnpm 2024-02-28 16:33:15 +03:00
vchikalkin
79e707a232 apps/web: add sentry scope to set user on client 2024-02-24 17:14:06 +03:00
vchikalkin
d374208097 Sentry: disable mask inputs 2024-02-24 16:24:00 +03:00
vchikalkin
557053caa6 apps/web: fix sentry trpc capture exception 2024-02-23 12:47:12 +03:00
vchikalkin
e258873976 apps/web: reconfigure sentry 2024-02-22 15:36:20 +03:00
vchikalkin
59e45ddc63 project: upgrade packages 2024-02-22 14:37:37 +03:00
vchikalkin
eaf95c6007 apps/web: upgrade next@14.1.0 2024-02-22 13:16:25 +03:00
vchikalkin
f3518244ab apps/api: reduce TTL GetConfigurations, GetDealers, GetModels 2024-02-21 22:38:07 +03:00
vchikalkin
c5683f7edd apps/web: fix irrInfo empty after loadKP 2024-02-18 18:34:32 +03:00
vchikalkin
acdacbf18e apps/api: manual headers for fetching from graphql 2024-02-18 17:40:36 +03:00
vchikalkin
ef88c12e74 apps/api: configure queries ttl 2024-02-18 17:12:12 +03:00
vchikalkin
ab0d455afb apps/api: add seconds util 2024-02-18 15:23:36 +03:00
vchikalkin
e0d2836f4a app/api: add feature to disable cache for some queries 2024-02-18 15:15:53 +03:00
vchikalkin
4bc5234d09 apps/api: add query-ttl config
proxyController: pass all request headers to proxied server
2024-02-18 15:00:36 +03:00
vchikalkin
6041082fec apps/web: move initial data fetch to client 2024-02-18 12:23:50 +03:00
vchikalkin
f426ca7f23 apps/web: disable disable webpack for build 2024-02-17 20:36:27 +03:00
vchikalkin
5115551b0e apps/api: fix expose port 2024-02-17 20:06:13 +03:00
vchikalkin
5ce9484e43 docker: fix build api 2024-02-17 19:24:01 +03:00
vchikalkin
8a117e250c merge branch feature/optimize-graphql-queries 2024-02-17 17:39:31 +03:00
vchikalkin
8089f62c31 process/fingap: fix getFingapRisks 2024-02-16 17:24:45 +03:00
vchikalkin
bd775babf9 server: make getTarif public procedure 2024-02-16 13:53:41 +03:00
vchikalkin
e222a3093a process/fingap & process/supplier-agent: remove inline graphql queries 2024-02-16 13:32:16 +03:00
vchikalkin
53debf2cff server: combine miltiple quote query to single 2024-02-16 13:22:01 +03:00
vchikalkin
c092b8f47c init/get-main-data: disable some queries 2024-02-16 12:08:14 +03:00
vchikalkin
6b7c679a0d process/configurator: fix irr-addon reset loading indication 2024-02-14 16:01:14 +03:00
vchikalkin
7a00478727 Form/CreateKP: изменили расположение элементов 2024-02-12 15:26:52 +03:00
vchikalkin
677ab1d75b process/configurator: удалили реакцию заполнения tbxIRR_Perc из тарифа
добавили валидацию tbxIRR_Perc на диапазон irrInfo
2024-02-12 15:16:51 +03:00
vchikalkin
126302a10a merge branch feature/loading-tarif-indicator 2024-02-12 13:25:11 +03:00
vchikalkin
35ce71c149 process/configurator: abort prev getTarif query 2024-02-08 13:19:51 +03:00
vchikalkin
47b57c0c73 apps/web: get selectTarif options on load-kp 2024-02-08 10:56:30 +03:00
obarykina
46c0b305b6 [2] merge branch release/dyn-3974_full-price-nds 2024-02-08 09:46:56 +03:00
vchikalkin
eb6f0ef949 merge branch fix/dyn-3977_get-tarifs 2024-02-07 16:21:22 +03:00
obarykina
7ca4e41980 components/calulation/addons/product-addon: show cbxFloatingRate 2024-02-07 15:48:34 +03:00
obarykina
662b04be0f merge branch release/dyn-3965_filter-telematic-electric-car 2024-02-07 15:42:52 +03:00
obarykina
e2d37712ad merge branch release/dyn-3974_full-price-nds 2024-02-07 15:34:17 +03:00
vchikalkin
4c3d305e7f apps/web: hide cbxFloatingRate from product-addon
add cbxPartialVAT & cbxFloatingRate to unlimited
2024-02-01 09:30:02 +03:00
vchikalkin
1aaa84e31d merge branch release/dyn-3854_floating-rate_partial-nds 2024-02-01 09:26:44 +03:00
vchikalkin
5a290d5be9 merge branch release/dyn-3855_kasko-validation 2024-02-01 08:58:19 +03:00
vchikalkin
ac14a86c33 process/recalc: fix firstPaymentPerc validation 2024-01-23 16:58:48 +03:00
vchikalkin
175cb63515 apps/web: calculate: use values.disableChecks to DISABLE_CHECKS_RESULTS 2024-01-16 11:24:36 +03:00
vchikalkin
803b4a1cfb project: remove .npmrc 2024-01-14 16:31:55 +03:00
vchikalkin
709eda1b50 build: add SENTRYCLI_CDNURL to docker configs 2024-01-14 16:23:25 +03:00
vchikalkin
bdc6d032aa apps/web: remove sharp from packages 2024-01-14 15:29:23 +03:00
vchikalkin
3462395818 apps/web: fix Dockerfile 2023-12-24 01:28:00 +03:00
vchikalkin
9308417c4d docker-compose.yml: remove depends_on 2023-12-24 01:20:14 +03:00
vchikalkin
d0f11cbc63 project: add docker-compose.yml 2023-12-24 01:06:36 +03:00
vchikalkin
eb1eb44769 merge release/dyn-3749_promotion 2023-12-12 08:56:09 +03:00
vchikalkin
6c300c8688 Revert "merge refactor/grid-styles"
This reverts commit 0c0dc7de5333a9d3710a161ec6432408b38174d9.
2023-12-11 15:43:15 +03:00
vchikalkin
3898fc1bdf apps/web: fix manifest 401 2023-12-01 16:27:55 +03:00
vchikalkin
0c0dc7de53 merge refactor/grid-styles 2023-12-01 16:16:14 +03:00
vchikalkin
703cee467c merge release/dyn-3643_minor-updates-november-2023 2023-12-01 10:48:45 +03:00
vchikalkin
c154568bb4 merge release/dyn-2803_sale-post-pi 2023-11-27 11:06:14 +03:00
vchikalkin
8dd9627849 merge refactor/error-monitoring 2023-10-04 17:36:52 +03:00
vchikalkin
07c87d583f merge fix/evo_baseproducts-filter 2023-09-21 10:36:25 +03:00
vchikalkin
0ff6ba3f74 merge fix/elt-remove-caching 2023-09-18 10:21:18 +03:00
vchikalkin
ae0b72aebd merge fix/dyn-3363_osago-kasko-leaseback 2023-09-06 15:40:08 +03:00
vchikalkin
10f3760c63 merge release/dyn-3089_year-parallel-import 2023-08-28 10:25:03 +03:00
vchikalkin
db29a77d37 merge release/dyn-3268_result-bonus-dop-prod 2023-08-25 12:59:56 +03:00
vchikalkin
0936d1cdd7 merge fix/dyn-3291_safe-finance 2023-08-24 15:21:00 +03:00
vchikalkin
5c039597b3 constants/value: change RATE to 15.9 2023-08-23 13:38:05 +03:00
vchikalkin
17a9810878 merge branch fix/dyn-3267_elt 2023-08-23 13:33:16 +03:00
vchikalkin
438242ac31 eslint: specify next app rootDir 2023-08-01 14:14:18 +03:00
vchikalkin
78064edf81 merge project/eslint-rules 2023-08-01 13:41:44 +03:00
vchikalkin
565cabb9bd merge fix/dyn-3167_elt 2023-08-01 10:03:10 +03:00
vchikalkin
903b32084a [2] Calculation/config: increase Min/Max PriceChange 2023-07-19 14:49:27 +03:00
vchikalkin
0f61d4a399 merge branch fix/kp-data_price-change 2023-07-17 14:04:58 +03:00
vchikalkin
7df69f92e8 eslint: upgrade config-canonical 2023-07-12 12:27:49 +03:00
vchikalkin
58d7a05591 merge experimental/error-monitoring 2023-07-12 11:14:13 +03:00
vchikalkin
802885e726 Calculation/config: increase Min/Max PriceChange 2023-07-06 13:30:01 +03:00
vchikalkin
caeb304d15 process/configurator: format min, max validation number 2023-07-06 13:28:52 +03:00
vchikalkin
d4c70101f2 merge branch release/dyn-2954_elt-restriction 2023-07-06 11:18:06 +03:00
vchikalkin
6fd64a62bf merge branch release/dyn-3047_min-max-validation 2023-07-05 12:58:22 +03:00
vchikalkin
11820a7b5d process/insurance: add fingap validation 2023-07-04 11:19:43 +03:00
vchikalkin
f9eda3c6b0 merge branch fix/dyn-3021_fingap 2023-07-04 10:42:55 +03:00
vchikalkin
c77f60fdc3 merge branch upgrade/packages-2606 2023-07-04 10:40:25 +03:00
vchikalkin
84de7a84e8 fix: process/supplier-agent: get-kp-data 2023-06-20 14:52:14 +03:00
vchikalkin
498fa217fe process/leasing-object: fix importerRewardPerc, importerRewardRub (get-kp-data) 2023-06-15 10:28:06 +03:00
vchikalkin
0f32abc6b2 process/payments: fix degression & firstPaymentPerc 2023-06-09 15:13:16 +03:00
vchikalkin
7ce8104b87 process/fingap: pass insCost from kp 2023-06-08 17:14:46 +03:00
vchikalkin
0952ce12ee process/insurance: add fingap.policyType validation 2023-06-08 16:45:05 +03:00
vchikalkin
b90bfc74d9 process/fingap: fix reaction 2023-06-08 16:21:51 +03:00
vchikalkin
d06b6e20d5 process/insurance: add fingap.insCost validation 2023-06-08 14:05:55 +03:00
vchikalkin
654865e845 fix: Expected number, received null 2023-06-07 09:48:34 +03:00
vchikalkin
4fd5c3602e process/insurance: add fingap.insured validation 2023-06-06 18:30:00 +03:00
vchikalkin
4a65a39ba3 process:subsidy: fix subsidySum ,importProgramSum 2023-06-06 12:23:45 +03:00
vchikalkin
e9edf189e7 fix: insurance-table fingap row 2023-06-05 13:42:46 +03:00
vchikalkin
e6289b7174 [2] fix: recalc disabled agents fields 2023-06-05 11:55:35 +03:00
vchikalkin
aaa8cadc02 fix: reset result values after create-kp with recalc 2023-06-05 11:31:40 +03:00
vchikalkin
554a702e6c fix: recalc disabled agents fields 2023-06-05 10:28:48 +03:00
vchikalkin
8e65d864ef fix: при подгрузке КП если в Салоне 2 ЮЛ, то слетает выбранный второй из списка и указывается первый по списку и соответственно Ав второго из списка тоже слетает 2023-06-05 10:18:29 +03:00
vchikalkin
2090056b81 fix: при подгрузке КП отрабатывает реакция на изменение продукта и есть продукт с частичным НДС то БУ ставится автоматом Да 2023-06-05 10:09:58 +03:00
vchikalkin
159fb85e63 fix: selectDealerPerson evo_inn/evo_kpp 2023-06-02 15:44:52 +03:00
vchikalkin
56b6510933 Form/ELT: fix kasko empty error message 2023-06-01 17:42:15 +03:00
vchikalkin
6a828ce82b process/configurator: fix tbxIRR_Perc reset 2023-06-01 16:14:42 +03:00
vchikalkin
074de953c9 process/gibdd: add selectRegionRegistration filter 2023-06-01 15:57:22 +03:00
vchikalkin
fe614b2529 fix: resetValues after get calculate results 2023-06-01 15:13:31 +03:00
vchikalkin
b7ae72d4c0 server/calculate: fix preparedValues districtRate 2023-06-01 14:24:42 +03:00
vchikalkin
805dc4dba3 process/add-product: fix telematic/tracker, recalcWithRevision 2023-06-01 14:22:36 +03:00
vchikalkin
290526b933 fix: RetryButton & SupportButton 2023-06-01 12:34:35 +03:00
vchikalkin
fd972aadad ui: remove page margin for desktop 2023-06-01 11:54:58 +03:00
vchikalkin
78c2446945 Calculation/Settings: move btnCreateKP & linkDownloadKp elements 2023-05-31 17:22:24 +03:00
vchikalkin
9a7e074863 ui: fix Form/InsuranceTable on laptops 2023-05-31 16:53:42 +03:00
vchikalkin
9ea4eeb6ce Revert "process/create-kp: auto download kp file"
This reverts commit cadfec5fc2c5cf85697aa4218e6b0c95de93d7d2.
2023-05-31 12:39:26 +03:00
vchikalkin
cadfec5fc2 process/create-kp: auto download kp file 2023-05-31 11:56:16 +03:00
vchikalkin
5c8560e266 store: add useErrors hook
Calculation/render: override btnCalculate, btnCreateKP, btnCreateKPMini
2023-05-31 11:45:59 +03:00
vchikalkin
2d0117bc4c Calculation/Form: disable elements when Calculate || CreateKP 2023-05-31 11:02:03 +03:00
vchikalkin
05ed1b6aef Calculation/Form: disable elements when LoadKP 2023-05-31 10:26:50 +03:00
vchikalkin
4f509aefdf process/configurator: fix selectRate value when load-kp 2023-05-30 15:25:11 +03:00
vchikalkin
befa92fbce fix: download-kp url 2023-05-30 14:45:47 +03:00
vchikalkin
f4acf8c316 elements: fix linkDownloadKp 2023-05-30 14:03:44 +03:00
vchikalkin
c9523328da fix: exclude values reset results after create-kp 2023-05-30 13:45:25 +03:00
vchikalkin
0ed21711db Components/Common: fix extra error Buttons
config/users: add roles
2023-05-30 11:40:28 +03:00
vchikalkin
c98167e609 ui: Form/AddProduct: fix selectInsNSIB 2023-05-30 11:25:03 +03:00
vchikalkin
feb72a3021 ui: Output/Results: sort results values 2023-05-30 11:20:46 +03:00
vchikalkin
8e3fb86169 leaseObjectUsed improvements 2023-05-29 11:59:02 +03:00
vchikalkin
6a95fbb3f2 process/price: fix NaN 2023-05-25 16:39:56 +03:00
vchikalkin
ab5700dd92 merge fix/migration/elt 2023-05-25 13:46:47 +03:00
vchikalkin
383b70434f process: make elt error notifications silent 2023-05-24 15:12:12 +03:00
vchikalkin
f8adc2e8c7 Form/Settings: fix tbxIRR_Perc min/max validation 2023-05-24 12:58:23 +03:00
vchikalkin
0246916fca process/calculate: reset results on change form values 2023-05-24 12:55:53 +03:00
vchikalkin
4383bc54b3 Components/Output: set active key validation if hasErrors 2023-05-24 11:46:01 +03:00
vchikalkin
2800184cfe Components/Output: set active key after calculation results 2023-05-24 11:29:12 +03:00
vchikalkin
616313eb20 merge release/dyn-2846_pi 2023-05-19 13:11:42 +03:00
vchikalkin
96e792b91d fix PaymentsTable 2023-05-16 14:13:19 +03:00
vchikalkin
1d5328db73 beautify imports/exports after 4549448 2023-05-16 13:50:47 +03:00
vchikalkin
45494483a0 fix build 2023-05-16 13:42:46 +03:00
vchikalkin
7fb706cdaf ui: fix elements layout on mobile devices 2023-05-16 12:04:01 +03:00
vchikalkin
d620cf0c92 optimize package/ui/elements exports 2023-05-16 12:03:54 +03:00
vchikalkin
5940f4c048 elt/validation: remove supplierDiscountRub 2023-05-15 09:22:34 +03:00
vchikalkin
bb6a3986b1 elt: extend validation
extend reset reaction triggers
fix make-request/osago
2023-05-15 09:15:31 +03:00
vchikalkin
2bea56fa87 process/configurator: fix get IRR_Perc from kp 2023-05-12 12:38:35 +03:00
vchikalkin
d195530b15 tbxVIN: capitalize letters 2023-05-12 12:07:49 +03:00
vchikalkin
04b7def049 merge migration/elt 2023-05-12 11:34:09 +03:00
vchikalkin
a2471a0ca8 style: global scrollbar style 2023-04-21 13:26:21 +03:00
vchikalkin
c9a9f3a66d Components/Output/PaymentsTable: disable pagination 2023-04-21 13:01:05 +03:00
vchikalkin
910c5627d2 merge release/unlimited-1904 2023-04-21 11:13:58 +03:00
vchikalkin
860ecb9384 merge migration/fix-4 2023-04-19 22:34:50 +03:00
vchikalkin
87e4783dd5 merge migration/fix-3 & migration/upgrade-code-130423 2023-04-14 10:57:02 +03:00
vchikalkin
0d35042e41 merge migration/fix-2 2023-04-10 11:58:50 +03:00
vchikalkin
95040c5a10 06e7687: fix download-kp url 2023-04-04 15:10:14 +03:00
vchikalkin
06e7687fc1 trpc/create-kp: add downloadKp url to result 2023-04-04 13:27:07 +03:00
vchikalkin
79179c10f3 merge migration/fix-1 2023-04-03 12:50:47 +03:00
vchikalkin
5e2fd20520 eslint-staged: ignore /graphql 2023-04-03 12:50:23 +03:00
vchikalkin
3e7d350248 Revert "trpc: check unlimited in middleware"
This reverts commit 90c0ef481b03bfb7d1a822dfa9e667f59f2c27b2.
2023-04-03 10:16:46 +03:00
vchikalkin
618d8cca58 project: add lint-staged 2023-04-03 10:10:18 +03:00
vchikalkin
90c0ef481b trpc: check unlimited in middleware 2023-03-29 15:10:34 +03:00
vchikalkin
1ea11f8eff change support url 2023-03-29 12:24:16 +03:00
vchikalkin
e8d1057c69 merge feature/unlimited 2023-03-29 10:07:34 +03:00
vchikalkin
5442905966 merge migration/random-4 2023-03-28 09:33:17 +03:00
vchikalkin
9d45a7bff0 merge migration/random-3 2023-03-16 15:35:44 +03:00
vchikalkin
29460f4509 fix load-kp error 2023-02-28 15:28:39 +03:00
vchikalkin
44ccd85e86 upgrade packages #2 2023-02-28 10:39:17 +03:00
vchikalkin
57c28c741e packages/tools: remove radash 2023-02-28 10:30:32 +03:00
vchikalkin
d01047752f upgrade packages 2023-02-28 10:18:52 +03:00
vchikalkin
d1df501f86 merge migration/random-2 2023-02-28 09:46:31 +03:00
vchikalkin
b786f49aa1 vscode: fix client debug 2023-02-06 14:30:10 +03:00
vchikalkin
4f972a4cfe git: regenerate .gitignore 2023-02-06 12:50:34 +03:00
vchikalkin
b15de1ffe2 merge branch dev/eslint-config 2023-02-06 12:19:39 +03:00
vchikalkin
96c4a095b1 merge migration/random-1 2023-02-03 08:30:24 +03:00
vchikalkin
d6db50a582 merge migration/fix/agents 2023-01-18 15:44:15 +03:00
vchikalkin
fa3b6a2390 mocks: set default user akalinina 2023-01-18 15:40:56 +03:00
vchikalkin
24e4d8cd7d merge release/turborepo 2023-01-11 11:26:08 +03:00
vchikalkin
259a63f787 fix build 2022-12-27 13:59:50 +03:00
vchikalkin
e77a8e03d4 merge release/environment-variables 2022-12-27 13:10:09 +03:00
vchikalkin
e2bbbaaeab packages: upgrade to next@13 2022-12-21 13:50:41 +03:00
vchikalkin
ff6d7f57a2 packages/ui: fix layout imports 2022-12-21 12:26:06 +03:00
vchikalkin
3dbef34d33 next.config.js: use next-composed-plugins 2022-12-21 11:04:54 +03:00
vchikalkin
156c6c51b5 project: use yarn workspaces
(merge branch release/monorepo)
2022-12-20 22:04:45 +03:00
602 changed files with 53697 additions and 25799 deletions

2
.env
View File

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

View File

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

11
.eslintrc.js Normal file
View File

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

View File

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

58
.gitignore vendored
View File

@ -1,19 +1,44 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
### 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 # dependencies
/node_modules node_modules
/.pnp .pnp
.pnp.js .pnp.js
# testing # testing
/coverage coverage
# next.js # next.js
/.next/ .next/
/out/ out/
# production # production
/build build
# misc # misc
.DS_Store .DS_Store
@ -34,11 +59,14 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
# Yarn # End of https://www.toptal.com/developers/gitignore/api/nextjs
.pnp.* # Created by https://www.toptal.com/developers/gitignore/api/turbo
.yarn/* # Edit at https://www.toptal.com/developers/gitignore?templates=turbo
!.yarn/patches
!.yarn/plugins ### Turbo ###
!.yarn/releases # Turborepo task cache
!.yarn/sdks .turbo
!.yarn/versions
# End of https://www.toptal.com/developers/gitignore/api/turbo
.pnpm

View File

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

View File

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

View File

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

10
.vscode/launch.json vendored
View File

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

18
.vscode/settings.json vendored
View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,13 +0,0 @@
/* 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

@ -1,67 +0,0 @@
/* 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

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

View File

@ -1,14 +0,0 @@
/* 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

@ -1,38 +0,0 @@
/* 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={payment.num === 0 ? 0 : 0.01}
max={100}
step={1}
precision={payment.num === 0 ? 4 : 2}
/>
);
},
},
];

View File

@ -1,28 +0,0 @@
/* 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

@ -1,89 +0,0 @@
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

@ -1,20 +0,0 @@
/* 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

@ -1,21 +0,0 @@
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

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

View File

@ -1,27 +0,0 @@
/* 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

@ -1,37 +0,0 @@
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

@ -1,23 +0,0 @@
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,153 +0,0 @@
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

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

View File

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

View File

@ -1,13 +0,0 @@
/* eslint-disable import/prefer-default-export */
import Button from 'Elements/Button';
import Result from 'Elements/Result';
function handleRetry() {
window.location.reload();
}
const RetryButton = <Button action={handleRetry} text="Попробовать еще раз" />;
export function CRMError() {
return <Result status="500" title="500" subTitle="Ошибка соединения с CRM" extra={RetryButton} />;
}

View File

@ -1,41 +0,0 @@
import Image from 'next/image';
import logo from 'public/assets/images/logo-primary.svg';
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;
}
img {
filter: brightness(0) invert(1);
}
`;
const LogoText = styled.h3`
margin: 0;
text-transform: uppercase;
color: #fff;
font-size: 0.85rem;
font-family: 'Montserrat';
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

@ -1,13 +0,0 @@
/* 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

@ -1,40 +0,0 @@
/* 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

@ -1,32 +0,0 @@
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

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

View File

@ -1,76 +0,0 @@
/* 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

@ -1,46 +0,0 @@
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

@ -1,89 +0,0 @@
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';
const Bold = styled.span`
font-weight: bold;
`;
function Message(title, text) {
return (
<>
<Bold>{title}</Bold>
{': '}
{text}
</>
);
}
const AlertWrapper = styled(Box)`
margin: 0 0 5px 0;
`;
function getElementsErrors($calculation) {
const errors = Object.values($calculation.$validation).map((validation) => {
const elementErrors = validation.getMessages();
const elementTitle = validation.params.err_title;
return elementErrors.map((error) => (
<AlertWrapper>
<Alert key={error.name} type="error" showIcon message={Message(elementTitle, error)} />
</AlertWrapper>
));
});
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 hasElementsErrors = Object.values($calculation.$validation).some(
(validation) => validation.hasErrors
);
const hasPaymentsErrors = $tables.payments.validation.hasErrors;
const hasInsuranceErrors = $tables.insurance.validation.hasErrors;
if (!hasElementsErrors && !hasPaymentsErrors && !hasInsuranceErrors) {
return <Alert type="success" showIcon message="Ошибок нет 🙂" />;
}
const elementsErrors = getElementsErrors($calculation);
const paymentsErrors = getPaymentsTableErrors($tables);
const insuranceErrors = getInsuranceTableErrors($tables);
const errors = [...elementsErrors, ...paymentsErrors, ...insuranceErrors];
return <Flex flexDirection="column">{errors}</Flex>;
});
function Validation() {
return (
<Box>
<Errors />
</Box>
);
}
export default {
id: 'validation',
title: 'Ошибки',
Component: Validation,
};

View File

@ -1,35 +0,0 @@
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;

View File

@ -1,69 +0,0 @@
# 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_BASE_PATH
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_PROXY
ARG NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT
ARG NEXT_PUBLIC_URL_GET_USER_PROXY
ARG NEXT_PUBLIC_URL_GET_USER_DIRECT
ARG NEXT_PUBLIC_URL_CORE_FINGAP_PROXY
ARG NEXT_PUBLIC_URL_CORE_FINGAP_DIRECT
# 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"]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +0,0 @@
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,34 +1,81 @@
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). # Turborepo starter
## Getting Started This is an official starter Turborepo.
First, run the development server: ## Using this example
```bash Run the following command:
npm run dev
# or ```sh
yarn dev npx create-turbo@latest
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. ## What's inside?
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. This Turborepo includes the following packages/apps:
[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`. ### Apps and Packages
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. - `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
## Learn More Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
To learn more about Next.js, take a look at the following resources: ### Utilities
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. This Turborepo has some additional tools already setup for you:
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - [TypeScript](https://www.typescriptlang.org/) for static type checking
- [ESLint](https://eslint.org/) for code linting
- [Prettier](https://prettier.io) for code formatting
## Deploy on Vercel ### Build
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. To build all apps and packages, run the following command:
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. ```
cd my-turborepo
pnpm build
```
### Develop
To develop all apps and packages, run the following command:
```
cd my-turborepo
pnpm dev
```
### Remote Caching
Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup), then enter the following commands:
```
cd my-turborepo
npx turbo login
```
This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
```
npx turbo link
```
## Useful Links
Learn more about the power of Turborepo:
- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)

View File

@ -1,11 +0,0 @@
/* 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,20 +0,0 @@
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`),
};

View File

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

View File

@ -1,16 +0,0 @@
/* eslint-disable import/prefer-default-export */
import type { QueryFunctionContext } from '@tanstack/react-query';
import axios from 'axios';
import type { RequestFinGAP, ResponseFinGAP } from './types';
export async function calculateFinGAP(payload: RequestFinGAP, { signal }: QueryFunctionContext) {
const { data } = await axios.post<ResponseFinGAP>(
process.env.NEXT_PUBLIC_URL_CORE_FINGAP_PROXY!,
payload,
{
signal,
}
);
return data;
}

View File

@ -1,16 +0,0 @@
/* eslint-disable import/prefer-default-export */
import type { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { love } from './tools';
import type { User } from './types';
// prettier-ignore
const uri = typeof window === 'undefined'
? process.env.NEXT_PUBLIC_URL_GET_USER_DIRECT
: process.env.NEXT_PUBLIC_URL_GET_USER_PROXY;
export async function getUser(config: AxiosRequestConfig) {
const user = await axios.get<User>(uri!, config).then((res) => love(res.data));
return user;
}

View File

@ -1,10 +0,0 @@
/* eslint-disable import/prefer-default-export */
import type { User } from './types';
export function love(user: User) {
const superUsers: string[] = JSON.parse(process.env.USERS_SUPER || '');
// eslint-disable-next-line no-param-reassign
if (superUsers?.includes(user.username)) user.displayName += '🧡';
return user;
}

View File

@ -1,12 +0,0 @@
/** @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/**/*'],
},
};

View File

@ -1,35 +0,0 @@
/* 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;
}

7
apps/api/.dockerignore Normal file
View File

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

13
apps/api/.eslintrc.js Normal file
View File

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

56
apps/api/.gitignore vendored Normal file
View File

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

4
apps/api/.prettierrc Normal file
View File

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

45
apps/api/Dockerfile Normal file
View File

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

73
apps/api/README.md Normal file
View File

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

8
apps/api/nest-cli.json Normal file
View File

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

74
apps/api/package.json Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

15
apps/api/src/main.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

22
apps/api/tsconfig.json Normal file
View File

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

View File

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

8
apps/web/.eslintignore Normal file
View File

@ -0,0 +1,8 @@
.next
public
apollo.config.js
mocks
graphql/crm.schema.graphql
graphql/crm.types.ts
package.json
next-env.d.ts

14
apps/web/.eslintrc.js Normal file
View File

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

4
apps/web/.gitignore vendored Normal file
View File

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

View File

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

19
apps/web/@types/errors.ts Normal file
View File

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

View File

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

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