Calculation: add InsuranceTable

This commit is contained in:
Chika 2022-06-22 22:19:12 +03:00
parent e36241af56
commit c6f73b6b8f
18 changed files with 338 additions and 11 deletions

View File

@ -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<T>(key: string, Component: ComponentType<T>, valueName: Keys) {
return observer((props: T) => {
const [value, setValue] = useInsuranceValue(key, valueName);
const options = useRowOptions(valueName);
return <Component value={value} options={options} setValue={setValue} {...props} />;
});
}
export function buildValueComponent<T>(key: string, Component: ComponentType<T>, valueName: Keys) {
return observer((props: T) => {
const [value, setValue] = useInsuranceValue(key, valueName);
return <Component value={value} setValue={setValue} {...props} />;
});
}

View File

@ -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<Insurance.Row> = [
{
key: 'policyType',
dataIndex: 'policyType',
title: 'Тип полиса',
},
{
key: 'insuranceCompany',
dataIndex: 'insuranceCompany',
title: 'Страховая компания',
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insuranceCompany');
return <Component />;
},
},
{
key: 'insured',
dataIndex: 'insured',
title: 'Плательщик',
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insured');
return <Component />;
},
},
{
key: 'insCost',
dataIndex: 'insCost',
title: 'Стоимость за первый период',
render: (_, record) => {
const Component = buildValueComponent(record.key, InputNumber, 'insCost');
return <Component min={0} max={MAX_INSURANCE} step={1000} precision={2} />;
},
},
{
key: 'insTerm',
dataIndex: 'insTerm',
title: 'Срок страхования',
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insTerm');
return <Component />;
},
},
];

View File

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

View File

@ -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 (
<Wrapper>
<Table
size="small"
pagination={false}
columns={columns}
dataSource={values}
scroll={{
x: true,
}}
/>
</Wrapper>
);
}
export default InsuranceTable;

View File

@ -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<Row[Key]>[];
};

View File

@ -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 (
<Flex flexDirection="column">
{renderedRows}
{/* TODO: add Insurance Table */}
<InsuranceTable />
{/* TODO: add FinGAP Table */}
</Flex>
);

View File

@ -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({
/>
</FormItem>
);
}
} as FC<InputNumberProps>);
type InputNumberProps = AntInputNumberProps<number>;

View File

@ -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({
/>
</FormItem>
);
}
} as FC<SelectProps>);
export { type SelectProps } from 'antd';

4
Elements/Table.js Normal file
View File

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

View File

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

View File

@ -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,
},
];

View File

@ -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<PageProps> = async (ctx) =>
props: {
user,
initialApolloState: apolloClient.cache.extract(),
tables: {
insurance: {
values: insuranceTableConfig.defaultRows,
options: insuranceTableConfig.defaultOptions,
},
},
},
};
};

View File

@ -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;

View File

@ -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);
}
}

10
stores/tables/index.ts Normal file
View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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<Insurance.Row>) => {
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];
}
}

View File

@ -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;
};
}