diff --git a/Components/Calculation/Form/Insurance/InsuranceTable/builders.tsx b/Components/Calculation/Form/Insurance/InsuranceTable/builders.tsx new file mode 100644 index 0000000..d0d715a --- /dev/null +++ b/Components/Calculation/Form/Insurance/InsuranceTable/builders.tsx @@ -0,0 +1,22 @@ +import { observer } from 'mobx-react-lite'; +import type { ComponentType } from 'react'; +import { useRowOptions } from 'stores/tables/insurance/hooks'; +import { useInsuranceValue } from './hooks'; +import type { Keys } from './types'; + +export function buildOptionComponent(key: string, Component: ComponentType, valueName: Keys) { + return observer((props: T) => { + const [value, setValue] = useInsuranceValue(key, valueName); + const options = useRowOptions(valueName); + + return ; + }); +} + +export function buildValueComponent(key: string, Component: ComponentType, valueName: Keys) { + return observer((props: T) => { + const [value, setValue] = useInsuranceValue(key, valueName); + + return ; + }); +} diff --git a/Components/Calculation/Form/Insurance/InsuranceTable/config.tsx b/Components/Calculation/Form/Insurance/InsuranceTable/config.tsx new file mode 100644 index 0000000..96e1bc1 --- /dev/null +++ b/Components/Calculation/Form/Insurance/InsuranceTable/config.tsx @@ -0,0 +1,55 @@ +/* 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 { buildOptionComponent, buildValueComponent } from './builders'; +import type * as Insurance from './types'; + +export const columns: ColumnsType = [ + { + key: 'policyType', + dataIndex: 'policyType', + title: 'Тип полиса', + }, + { + key: 'insuranceCompany', + dataIndex: 'insuranceCompany', + title: 'Страховая компания', + render: (_, record) => { + const Component = buildOptionComponent(record.key, Select, 'insuranceCompany'); + + return ; + }, + }, + { + key: 'insured', + dataIndex: 'insured', + title: 'Плательщик', + render: (_, record) => { + const Component = buildOptionComponent(record.key, Select, 'insured'); + + return ; + }, + }, + { + key: 'insCost', + dataIndex: 'insCost', + title: 'Стоимость за первый период', + render: (_, record) => { + const Component = buildValueComponent(record.key, InputNumber, 'insCost'); + + return ; + }, + }, + { + key: 'insTerm', + dataIndex: 'insTerm', + title: 'Срок страхования', + render: (_, record) => { + const Component = buildOptionComponent(record.key, Select, 'insTerm'); + + return ; + }, + }, +]; diff --git a/Components/Calculation/Form/Insurance/InsuranceTable/hooks.js b/Components/Calculation/Form/Insurance/InsuranceTable/hooks.js new file mode 100644 index 0000000..12ffe6c --- /dev/null +++ b/Components/Calculation/Form/Insurance/InsuranceTable/hooks.js @@ -0,0 +1,28 @@ +/* eslint-disable import/prefer-default-export */ +import { useEffect, useState } from 'react'; +import { useRowValue } from 'stores/tables/insurance/hooks'; +import { useDebouncedCallback } from 'use-debounce'; + +export function useInsuranceValue(key, valueName) { + const [storeValue, setStoreValue] = useRowValue(key, valueName); + const [value, setValue] = useState(storeValue); + + // eslint-disable-next-line object-curly-newline + const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, 350, { maxWait: 1000 }); + + useEffect( + () => { + if (storeValue !== value) { + debouncedSetStoreValue(value); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [value] + ); + + useEffect(() => { + setValue(storeValue); + }, [storeValue]); + + return [value, setValue]; +} diff --git a/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx b/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx new file mode 100644 index 0000000..e1f75cb --- /dev/null +++ b/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx @@ -0,0 +1,32 @@ +import Table from 'Elements/Table'; +import { useStore } from 'stores/hooks'; +import styled from 'styled-components'; +import { columns } from './config'; + +const Wrapper = styled.div` + td > * { + margin: 0; + } +`; + +function InsuranceTable() { + const store = useStore(); + + const { values } = store.$tables.insurance; + + return ( + + + + ); +} + +export default InsuranceTable; diff --git a/Components/Calculation/Form/Insurance/InsuranceTable/types.ts b/Components/Calculation/Form/Insurance/InsuranceTable/types.ts new file mode 100644 index 0000000..131c2e3 --- /dev/null +++ b/Components/Calculation/Form/Insurance/InsuranceTable/types.ts @@ -0,0 +1,16 @@ +import type { BaseOption } from 'Elements/types'; + +export type Row = { + key: string; + policyType: string; + insuranceCompany: string | null; + insured: 100_000_000 | 100_000_001 | null; + insCost: number; + insTerm: 100_000_000 | 100_000_001 | null; +}; + +export type Keys = keyof Row; + +export type Options = { + [Key in keyof Row]?: BaseOption[]; +}; diff --git a/Components/Calculation/Form/Insurance/index.jsx b/Components/Calculation/Form/Insurance/index.jsx index 57009de..2a27df1 100644 --- a/Components/Calculation/Form/Insurance/index.jsx +++ b/Components/Calculation/Form/Insurance/index.jsx @@ -1,6 +1,7 @@ import renderFormRows from 'Components/Calculation/lib/render-rows'; import { Flex } from 'UIKit/grid'; import { id, rows, title } from './config'; +import InsuranceTable from './InsuranceTable'; function Insurance() { const renderedRows = renderFormRows(rows); @@ -8,7 +9,7 @@ function Insurance() { return ( {renderedRows} - {/* TODO: add Insurance Table */} + {/* TODO: add FinGAP Table */} ); diff --git a/Elements/InputNumber.tsx b/Elements/InputNumber.tsx index 10bc388..020fd6e 100644 --- a/Elements/InputNumber.tsx +++ b/Elements/InputNumber.tsx @@ -1,10 +1,11 @@ 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; -export default function InputNumber({ +export default (function InputNumber({ setValue, status, isValid, @@ -23,7 +24,7 @@ export default function InputNumber({ /> ); -} +} as FC); type InputNumberProps = AntInputNumberProps; diff --git a/Elements/Select.tsx b/Elements/Select.tsx index 5756db1..a89d483 100644 --- a/Elements/Select.tsx +++ b/Elements/Select.tsx @@ -1,4 +1,6 @@ +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'; @@ -8,7 +10,7 @@ type ElementProps = { options: BaseOption[]; }; -export default function Select({ +export default (function Select({ value = null, setValue, options = [], @@ -41,6 +43,6 @@ export default function Select({ /> ); -} +} as FC); export { type SelectProps } from 'antd'; diff --git a/Elements/Table.js b/Elements/Table.js new file mode 100644 index 0000000..2d7a5c8 --- /dev/null +++ b/Elements/Table.js @@ -0,0 +1,4 @@ +/* eslint-disable unicorn/filename-case */ +/* eslint-disable no-restricted-exports */ + +export { Table as default } from 'antd'; diff --git a/Elements/types.ts b/Elements/types.ts index cbffe17..e9d4e0f 100644 --- a/Elements/types.ts +++ b/Elements/types.ts @@ -3,12 +3,12 @@ export type Status = 'Default' | 'Disabled' | 'Loading' | 'Hidden'; export type BaseElementProps = { value: ValueType; setValue: (value: ValueType) => void; - status: Status; - isValid: boolean; - help: string; + status?: Status; + isValid?: boolean; + help?: string; }; -export type BaseOption = { +export type BaseOption = { label: string; - value: any; + value: T; }; diff --git a/config/tables/insurance-table.ts b/config/tables/insurance-table.ts new file mode 100644 index 0000000..128d4ba --- /dev/null +++ b/config/tables/insurance-table.ts @@ -0,0 +1,68 @@ +/* eslint-disable object-curly-newline */ +import type * as Insurance from 'Components/Calculation/Form/Insurance/InsuranceTable/types'; + +export const defaultOptions: Insurance.Options = { + insured: [ + { + label: 'ЛП', + value: 100_000_000, + }, + { + label: 'ЛД', + value: 100_000_001, + }, + ], + insTerm: [ + { + label: '12 месяцев', + value: 100_000_000, + }, + { + label: 'Срок ДЛ', + value: 100_000_001, + }, + ], +}; + +export const defaultRows: Insurance.Row[] = [ + { + key: 'osago', + policyType: 'ОСАГО', + insuranceCompany: null, + insured: 100_000_000, + insCost: 0, + insTerm: 100_000_000, + }, + { + key: 'kasko', + policyType: 'КАСКО', + insuranceCompany: null, + insured: 100_000_000, + insCost: 0, + insTerm: null, + }, + { + key: 'dgo', + policyType: 'ДГО', + insuranceCompany: null, + insured: null, + insCost: 0, + insTerm: null, + }, + { + key: 'ns', + policyType: 'НС', + insuranceCompany: null, + insured: null, + insCost: 0, + insTerm: null, + }, + { + key: 'finGAP', + policyType: 'Safe Finance', + insuranceCompany: null, + insured: 100_000_000, + insCost: 0, + insTerm: null, + }, +]; diff --git a/pages/index.tsx b/pages/index.tsx index 144f074..a769461 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,6 @@ import initializeApollo from 'apollo/client'; import Calculation from 'Components/Calculation'; +import * as insuranceTableConfig from 'config/tables/insurance-table'; import type { GetServerSideProps } from 'next'; import Head from 'next/head'; import { fetchUser } from 'services/user'; @@ -33,6 +34,12 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => props: { user, initialApolloState: apolloClient.cache.extract(), + tables: { + insurance: { + values: insuranceTableConfig.defaultRows, + options: insuranceTableConfig.defaultOptions, + }, + }, }, }; }; diff --git a/stores/index.js b/stores/index.js index 7a4be12..f6385a2 100644 --- a/stores/index.js +++ b/stores/index.js @@ -12,12 +12,19 @@ export function initializeStore(initialData) { const _store = store ?? new RootStore(); if (initialData) { - const { user = null, calculation } = initialData; + const { user = null, calculation, tables } = initialData; _store.$user.hydrate(user); if (calculation?.values) _store.$calculation.$values.hydrate(calculation.values); if (calculation?.statuses) _store.$calculation.$status.hydrate(calculation.statuses); if (calculation?.options) _store.$calculation.$options.hydrate(calculation.options); + + if (tables.insurance) { + _store.$tables.insurance.hydrate({ + values: tables.insurance.values, + options: tables.insurance.options, + }); + } } if (typeof window === 'undefined') return _store; diff --git a/stores/root.ts b/stores/root.ts index 0854ff0..82ddd94 100644 --- a/stores/root.ts +++ b/stores/root.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-cycle */ import { enableStaticRendering } from 'mobx-react-lite'; import CalculationStore from './calculation'; +import TablesStore from './tables'; import UserStore from './user'; enableStaticRendering(typeof window === 'undefined'); @@ -8,9 +9,11 @@ enableStaticRendering(typeof window === 'undefined'); export default class RootStore { $user: UserStore; $calculation: CalculationStore; + $tables: TablesStore; constructor() { this.$user = new UserStore(this); this.$calculation = new CalculationStore(this); + this.$tables = new TablesStore(this); } } diff --git a/stores/tables/index.ts b/stores/tables/index.ts new file mode 100644 index 0000000..b01bcd8 --- /dev/null +++ b/stores/tables/index.ts @@ -0,0 +1,10 @@ +import type RootStore from 'stores/root'; +import InsuranceTable from './insurance'; + +export default class TablesStore { + insurance: InsuranceTable; + + constructor(rootStore: RootStore) { + this.insurance = new InsuranceTable(rootStore); + } +} diff --git a/stores/tables/insurance/hooks.js b/stores/tables/insurance/hooks.js new file mode 100644 index 0000000..715bfbf --- /dev/null +++ b/stores/tables/insurance/hooks.js @@ -0,0 +1,23 @@ +import { useStore } from 'stores/hooks'; + +export function useRowValue(key, valueName) { + const { $tables } = useStore(); + + const storeValue = $tables.insurance.getRowValue(key, valueName); + + function setStoreValue(value) { + $tables.insurance.setRowValues(key, { + [valueName]: value, + }); + } + + return [storeValue, setStoreValue]; +} + +export function useRowOptions(valueName) { + const { $tables } = useStore(); + + const options = $tables.insurance.getRowOptions(valueName); + + return options; +} diff --git a/stores/tables/insurance/index.ts b/stores/tables/insurance/index.ts new file mode 100644 index 0000000..6d71bfe --- /dev/null +++ b/stores/tables/insurance/index.ts @@ -0,0 +1,44 @@ +/* eslint-disable object-curly-newline */ +import type * as Insurance from 'Components/Calculation/Form/Insurance/InsuranceTable/types'; +import { mergeWith } from 'lodash'; +import { makeAutoObservable } from 'mobx'; +import type RootStore from 'stores/root'; + +export interface InsuranceTableData { + values: Insurance.Row[]; + options: Insurance.Options; +} + +export default class InsuranceTable { + root: RootStore; + values: Insurance.Row[] = []; + options: Insurance.Options = {}; + + constructor(rootStore: RootStore) { + this.root = rootStore; + makeAutoObservable(this); + } + + hydrate = ({ values: initialValues, options: initialOptions }: InsuranceTableData) => { + this.values = initialValues; + this.options = initialOptions; + }; + + getRowValue(key: string, valueName: Insurance.Keys) { + const rowIndex = this.values.findIndex((x) => x.key === key); + + return this.values[rowIndex][valueName]; + } + + setRowValues = (key: string, row: Partial) => { + const rowIndex = this.values.findIndex((x) => x.key === key); + + if (rowIndex >= 0) { + mergeWith(this.values[rowIndex], row); + } + }; + + getRowOptions(valueName: Insurance.Keys) { + return this.options[valueName]; + } +} diff --git a/types/page.ts b/types/page.ts index f4cf353..cd3a3f1 100644 --- a/types/page.ts +++ b/types/page.ts @@ -3,6 +3,7 @@ import type { User } from 'services/user/types'; import type { CalculationOptions } from 'stores/calculation/options/types'; import type { CalculationStatuses } from 'stores/calculation/statuses/types'; import type { CalculationValues } from 'stores/calculation/values/types'; +import type { InsuranceTableData } from 'stores/tables/insurance'; export interface BasePageProps { user: User; @@ -12,4 +13,7 @@ export interface BasePageProps { statuses?: CalculationStatuses; options?: CalculationOptions; }; + tables: { + insurance: InsuranceTableData; + }; }