UNPKG

react-application-core

Version:

A react-based application core for the business applications.

519 lines (416 loc) 20.4 kB
# React Application Core A react-based application core for the business applications. [![Build Status](https://travis-ci.org/apoterenko/react-application-core.svg?branch=master)](https://travis-ci.org/apoterenko/react-application-core) # Flow ![Flow](flow.png) # Description The library is designed to quickly start develop the business applications are based on React, Redux, Material-UI. # Demo * [react-redux-typescript-webpack-seed](https://github.com/apoterenko/react-redux-typescript-webpack-seed) * [Live demo](https://apoterenko.github.io/react-redux-typescript-webpack-seed) # Dependencies * [react](https://github.com/facebook/react) * [redux](https://github.com/reactjs/redux) * [react-redux](https://github.com/reactjs/react-redux) * [react-router-dom](https://github.com/ReactTraining/react-router) * [material-components-web](https://github.com/material-components/material-components-web) * [ramda](https://github.com/ramda/ramda) * [InversifyJS](https://github.com/inversify/InversifyJS) * [@dagrejs/graphlib](https://github.com/dagrejs/graphlib) * [moment](https://github.com/moment/moment) * [axios](https://github.com/axios/axios) * [store](https://github.com/marcuswestin/store.js) * [socket.io-client](https://github.com/socketio/socket.io-client) * [react-text-mask](https://github.com/text-mask/text-mask) # Classes & markers #### @action-builder * [FilterFormDialogActionBuilder (08.09.2020)](src/core/action/filter-form-dialog-action.builder.ts) * [FormActionBuilder (08.05.2020)](src/core/action/form-action.builder.ts) * [PageToolbarActionBuilder](src/core/action/page-toolbar-action.builder.ts) #### @component-container-impl * [FilterFormDialogContainer (01.08.2020)](src/core/component/dialog/filter-form-dialog/filter-form-dialog.container.tsx) * [FormContainer (09.05.2020)](src/core/component/form/form.container.tsx) * [FormTabPanelContainer (30.07.2020)](src/core/component/tab-panel/form/form-tab-panel.container.tsx) * [PageToolbarContainer (31.07.2020)](src/core/component/toolbar/page/page-toolbar.container.tsx) * [SearchToolbarContainer (31.07.2020)](src/core/component/toolbar/search/search-toolbar.container.tsx) * [TabPanelContainer (30.07.2020)](src/core/component/tab-panel/tab-panel.container.tsx) * [ToolbarToolsContainer (10.06.2020)](src/core/component/toolbar-tools/toolbar-tools.container.tsx) * [UnsavedFormChangesDialogContainer (15.06.2020)](src/core/component/dialog/unsaved-form-changes-dialog/unsaved-form-changes-dialog.container.tsx) #### @component-impl * [Calendar (29.12.2020)](src/core/component/calendar/calendar.component.tsx) * [Chart (18.12.2020)](src/core/component/chart/chart.component.tsx) * [Chip (02.06.2020)](src/core/component/chip/chip.component.tsx) * [ChipsField (16.06.2020)](src/core/component/field/chips-field/chips-field.component.tsx) * [CronField (28.12.2020)](src/core/component/field/cron-field/cron-field.component.tsx) * [Dialog & BaseDialog (10.10.2020)](src/core/component/dialog/base-dialog.component.tsx) * [DnD (18.10.2020)](src/core/component/dnd/dnd.component.tsx) * [Drawer (29.05.2020)](src/core/component/drawer/drawer.component.tsx) * [FileField (21.08.2020)](src/core/component/field/file-field/file-field.component.tsx) * [FormLayout (31.05.2020)](src/core/component/layout/form/form-layout.component.tsx) * [GridHead (20.05.2020)](src/core/component/grid/head/grid-head.component.tsx) * [Header (22.05.2020)](src/core/component/header/header.component.tsx) * [ListItem (17.08.2020)](src/core/component/list/item/list-item.component.tsx) * [Main (22.05.2020)](src/core/component/main/main.component.tsx) * [PageToolbar (10.06.2020)](src/core/component/toolbar/page/page-toolbar.component.tsx) * [SearchToolbar](src/core/component/toolbar/search/search-toolbar.component.tsx) * [Slider (16.10.2020)](src/core/component/slider/slider.component.tsx) * [SliderField (16.10.2020)](src/core/component/field/slider-field/slider-field.component.tsx) * [SubHeader (22.05.2020)](src/core/component/sub-header/sub-header.component.tsx) * [SubHeaderLink (22.05.2020)](src/core/component/sub-header-link/sub-header-link.component.tsx) * [TextField (21.08.2020)](src/core/component/field/text-field/text-field.component.tsx) * [Title (01.06.2020)](src/core/component/title/title.component.tsx) * [ToolbarTools (01.08.2020)](src/core/component/toolbar-tools/toolbar-tools.component.tsx) * [UnsavedFormChangesDialog (01.08.2020)](src/core/component/dialog/unsaved-form-changes-dialog/unsaved-form-changes-dialog.component.ts) #### @effects-proxy-factory [EffectsFactories](src/core/store/effects/effects.factory.ts) * [makeDestroyedContainerEffectsProxy (09.09.2020)](src/core/store/effects/destroyed-container-effects.proxy.ts) * [makeFilteredListEffectsProxy (09.09.2020)](src/core/store/effects/filtered-list-effects.proxy.ts) * [makeFilterFormDialogEffectsProxy (09.09.2020)](src/core/store/effects/filter-form-dialog-effects.proxy.ts) * [makeLoadedListOnTabActivateEffectsProxy (22.09.2020)](src/core/store/effects/loaded-list-on-tab-activate-effects.proxy.ts) * [makeSucceedEditedListEffectsProxy (10.09.2020)](src/core/store/effects/succeed-edited-list-effects.proxy.ts) #### @reducer * [formReducer](src/core/component/form/form.reducer.ts) * [queryFilterReducer](src/core/component/filter/query-filter.reducer.ts) #### @map * [map-as-component](src/core/util/map-as-component.ts) * [map-as-container](src/core/util/map-as-component.ts) * [map-as-original](src/core/util/map-as-original.ts) * [map-as-wrapper](src/core/util/map-as-wrapper.ts) * [map-as](src/core/util/map-as.ts) * [map-container-as-component](src/core/util/map-as-component.ts) * [map-entity-as-container](src/core/util/map-as-component.ts) * [mapper](src/core/util/mapper.ts) #### @middleware * [ChainedMiddlewareFactories (20.01.2021)](src/core/store/middleware/chained.middleware.ts) * [EditedListMiddlewareFactories (20.01.2021)](src/core/store/middleware/edited-list.middleware.ts) #### @configuration-entity * [IConfigurationDialogEntity](src/core/definition/dialog-definition.interface.ts) * [IConfigurationToolbarToolsEntity](src/core/definition/toolbar-tools-definition.interface.ts) #### @redux-holder-entity * [IReduxFormHolderEntity](src/core/definition/form-definition.interface.ts) * [IReduxUnsavedFormChangesDialogHolderEntity](src/core/definition/unsaved-form-changes-dialog-definition.interface.ts) # Utils * [EntityUtils (23.01.2021)](src/core/util/entity.ts) * [FnUtils (18.01.2021)](src/core/util/fn.ts) * [MultiFieldUtils (29.08.2020)](src/core/util/multi-field.ts) * [PageUtils (03.11.2020)](src/core/util/page.ts) * [ReducerUtils (23.12.2020)](src/core/util/reducer.ts) * [SelectOptionUtils (02.11.2020)](src/core/util/select-option.ts) * [SortUtils (14.10.2020)](src/core/util/sort.ts) * [StorageUtils (04.09.2020)](src/core/util/storage.ts) * [ValidatorUtils (22.01.2021)](src/core/util/validator.ts) # Theme customization (styling) #### Button * [_button.scss (13.05.2020)](src/core/component/button/_button.scss) * [_button-constant.scss (13.05.2020)](src/core/component/button/_button-constant.scss) * [_button-mixin.scss (13.05.2020)](src/core/component/button/_button-mixin.scss) #### Chart * [_chart.scss (23.05.2020)](src/core/component/chart/_chart.scss) * [_chart-constant.scss (23.05.2020)](src/core/component/chart/_chart-constant.scss) * [_chart-mixin.scss (23.05.2020)](src/core/component/chart/_chart-mixin.scss) #### Chip * [_chip.scss (02.06.2020)](src/core/component/chip/_chip.scss) * [_chip-constant.scss (02.06.2020)](src/core/component/chip/_chip-constant.scss) * [_chip-mixin.scss (02.06.2020)](src/core/component/chip/_chip-mixin.scss) #### DefaultLayout * [_default-layout.scss (27.05.2020)](src/core/component/layout/default/_default-layout.scss) * [_default-layout-constant.scss (27.05.2020)](src/core/component/layout/default/_default-layout-constant.scss) * [_default-layout-mixin.scss (27.05.2020)](src/core/component/layout/default/_default-layout-mixin.scss) #### Dialog * [_dialog.scss (10.10.2020)](src/core/component/dialog/_dialog.scss) * [_dialog-constant.scss (10.10.2020)](src/core/component/dialog/_dialog-constant.scss) * [_dialog-mixin.scss (10.10.2020)](src/core/component/dialog/_dialog-mixin.scss) #### DnD * [_dnd.scss (18.10.2020)](src/core/component/dnd/_dnd.scss) * [_dnd-constant.scss (18.10.2020)](src/core/component/dnd/_dnd-constant.scss) * [_dnd-mixin.scss (18.10.2020)](src/core/component/dnd/_dnd-mixin.scss) #### Drawer * [_drawer.scss (29.05.2020)](src/core/component/drawer/_drawer.scss) * [_drawer-constant.scss (29.05.2020)](src/core/component/drawer/_drawer-constant.scss) * [_drawer-mixin.scss (29.05.2020)](src/core/component/drawer/_drawer-mixin.scss) #### Field * [_field.scss (19.06.2020)](src/core/component/field/_field.scss) * [_field-constant.scss (19.06.2020)](src/core/component/field/_field-constant.scss) * [_field-mixin.scss (19.06.2020)](src/core/component/field/_field-mixin.scss) #### Form * [_form.scss (27.05.2020)](src/core/component/form/_form.scss) * [_form-constant.scss (27.05.2020)](src/core/component/form/_form-constant.scss) * [_form-mixin.scss (27.05.2020)](src/core/component/form/_form-mixin.scss) #### FormLayout * [_form-layout.scss (31.05.2020)](src/core/component/layout/form/_form-layout.scss) * [_form-layout-constant.scss (31.05.2020)](src/core/component/layout/form/_form-layout-constant.scss) * [_form-layout-mixin.scss (31.05.2020)](src/core/component/layout/form/_form-layout-mixin.scss) #### Header * [_header.scss (21.05.2020)](src/core/component/header/_header.scss) * [_header-constant.scss (21.05.2020)](src/core/component/header/_header-constant.scss) * [_header-mixin.scss (21.05.2020)](src/core/component/header/_header-mixin.scss) #### Main * [_main.scss (22.05.2020)](src/core/component/main/_main.scss) * [_main-constant.scss (22.05.2020)](src/core/component/main/_main-constant.scss) * [_main-mixin.scss (22.05.2020)](src/core/component/main/_main-mixin.scss) #### NavigationList * [_navigation-list.scss (15.05.2020)](src/core/component/navigation-list/_navigation-list.scss) * [_navigation-list-constant.scss (15.05.2020)](src/core/component/navigation-list/_navigation-list-constant.scss) * [_navigation-list-mixin.scss (15.05.2020)](src/core/component/navigation-list/_navigation-list-mixin.scss) #### PageToolbar * [_page-toolbar.scss (10.06.2020)](src/core/component/toolbar/page/_page-toolbar.scss) * [_page-toolbar-constant.scss (10.06.2020)](src/core/component/toolbar/page/_page-toolbar-constant.scss) * [_page-toolbar-mixin.scss (10.06.2020)](src/core/component/toolbar/page/_page-toolbar-mixin.scss) #### SubHeaderLink * [_sub-header-link.scss (22.05.2020)](src/core/component/sub-header-link/_sub-header-link.scss) * [_sub-header-link-constant.scss (22.05.2020)](src/core/component/sub-header-link/_sub-header-link-constant.scss) * [_sub-header-link-mixin.scss (22.05.2020)](src/core/component/sub-header-link/_sub-header-link-mixin.scss) #### TabPanel * [_tab-panel.scss (19.05.2020)](src/core/component/tab-panel/_tab-panel.scss) * [_tab-panel-constant.scss (19.05.2020)](src/core/component/tab-panel/_tab-panel-constant.scss) * [_tab-panel-mixin.scss (19.05.2020)](src/core/component/tab-panel/_tab-panel-mixin.scss) #### TextField * [_text-field.scss (18.06.2020)](src/core/component/field/text-field/_text-field.scss) * [_text-field-constant.scss (18.06.2020)](src/core/component/field/text-field/_text-field-constant.scss) #### Title * [_title.scss (01.06.2020)](src/core/component/title/_title.scss) * [_title-constant.scss (01.06.2020)](src/core/component/title/_title-constant.scss) * [_title-mixin.scss (01.06.2020)](src/core/component/title/_title-mixin.scss) # Usage #### Containers ###### Roles container ```typescript import * as React from 'react'; import { listWrapperMapper, filterWrapperMapper, defaultMappers, BaseContainer, DefaultLayoutContainer, ListContainer, ContainerVisibilityTypeEnum, actionsDisabledListWrapperEntityMapper, connector, SearchFieldToolbarContainer, } from 'react-application-core'; import { ROUTER_PATHS } from '../../app.routes'; import { IRolesContainerProps, ROLES_SECTION } from './roles.interface'; import { IAppState } from '../../app.interface'; import { AccessConfigT, IRoleEntity } from '../permission.interface'; import { AppPermissions } from '../../app.permissions'; @connector<IAppState, AccessConfigT>({ routeConfiguration: { type: ContainerVisibilityTypeEnum.PRIVATE, path: ROUTER_PATHS.ROLES, }, accessConfiguration: [AppPermissions.ROLES_VIEW], mappers: [ ...defaultMappers, (state) => filterWrapperMapper(state.roles), (state) => listWrapperMapper(state.roles) ], }) class RolesContainer extends BaseContainer<IRolesContainerProps> { public static defaultProps: IRolesContainerProps = { sectionName: ROLES_SECTION, }; public render(): JSX.Element { const props = this.props; const header = <SearchFieldToolbarContainer filterConfiguration={actionsDisabledListWrapperEntityMapper(props)} {...props}/>; return ( <DefaultLayoutContainer headerConfiguration={{ items: header }} {...props}> <ListContainer listConfiguration={{ itemConfiguration: { tpl: this.tpl }, useAddAction: this.permissionService.isAccessible(AppPermissions.ROLE_ADD), }} {...props}/> </DefaultLayoutContainer> ); } private tpl = (item: IRoleEntity): JSX.Element => ( <span> {item.name} {this.nc.id(item.id)} </span> ) } ``` ###### Role container ```typescript import * as React from 'react'; import { BaseContainer, FormContainer, FormDialog, TextField, toSelectOptions, FORM_DIALOG_REF, listWrapperSelectedEntityMapper, formMapper, DefaultLayoutContainer, defaultMappers, ChipsField, ContainerVisibilityTypeEnum, connector, LayoutBuilder, LayoutBuilderTypeEnum, } from 'react-application-core'; import { IRoleContainerProps, ROLE_SECTION } from './role.interface'; import { IAppState } from '../../../app.interface'; import { RIGHTS_DICTIONARY } from '../../../dictionary'; import { ROUTER_PATHS } from '../../../app.routes'; import { AccessConfigT } from '../../permission.interface'; import { AppPermissions } from '../../../app.permissions'; @connector<IAppState, AccessConfigT>({ routeConfiguration: { type: ContainerVisibilityTypeEnum.PRIVATE, path: ROUTER_PATHS.ROLE, }, accessConfiguration: [AppPermissions.ROLE_VIEW], mappers: [ ...defaultMappers, (state) => formMapper(state.roles.role), (state) => listWrapperSelectedEntityMapper(state.roles, state.roles.role) ], }) class RoleContainer extends BaseContainer<IRoleContainerProps> { public static defaultProps: IRoleContainerProps = { sectionName: ROLE_SECTION, }; private readonly layoutBuilder = new LayoutBuilder(); public render(): JSX.Element { const props = this.props; const dictionaries = props.dictionaries; const rights = dictionaries.rights && dictionaries.rights.data; const title = props.newEntity ? 'New role' : `Role ${this.nc.id(props.entityId)}`; return ( <DefaultLayoutContainer headerConfiguration={{ navigationActionType: 'arrow_back', onNavigationActionClick: this.activateFormDialog, }} title={title} {...props}> <FormContainer {...props}> { this.layoutBuilder.build({ layout: LayoutBuilderTypeEnum.VERTICAL, children: [ <TextField name='name' label='Name' autoFocus={true} required={true}/>, <ChipsField name='rights' label='Rights' options={toSelectOptions(rights)} bindDictionary={RIGHTS_DICTIONARY} menuConfiguration={{ useFilter: true, renderToCenterOfBody: true }} displayMessage='%d right(s)'/> ], }) } </FormContainer> <FormDialog ref={FORM_DIALOG_REF} onAccept={this.navigateToBack} {...props}/> </DefaultLayoutContainer> ); } } ``` #### Effects ###### Roles effects ```typescript import { EffectsService, IEffectsAction } from 'redux-effects-promise'; import { buildEntityRoute, provideInSingleton, ListActionBuilder, BaseEffects, effectsBy, makeFilteredListEffectsProxy, makeUntouchedListEffectsProxy, makeFailedListLoadEffectsProxy, makeEditedListEffectsProxy, } from 'react-application-core'; import { IApi } from '../../api/api.interface'; import { ROUTER_PATHS } from '../../app.routes'; import { ROLES_SECTION } from './roles.interface'; import { IRoleEntity } from '../permission.interface'; import { IAppState } from '../../app.interface'; import { ROLE_SECTION } from './role'; @provideInSingleton(RolesEffects) @effectsBy( makeUntouchedListEffectsProxy<IAppState>({ section: ROLES_SECTION, resolver: (state) => state.roles, }), makeEditedListEffectsProxy<IRoleEntity, IAppState>({ listSection: ROLES_SECTION, formSection: ROLE_SECTION, pathResolver: (role) => buildEntityRoute<IRoleEntity>(ROUTER_PATHS.ROLE, role), }), makeFilteredListEffectsProxy({ section: ROLES_SECTION }), makeFailedListLoadEffectsProxy(ROLES_SECTION) ) class RolesEffects extends BaseEffects<IApi> { @EffectsService.effects(ListActionBuilder.buildLoadActionType(ROLES_SECTION)) public $onRolesSearch(_: IEffectsAction, state: IAppState): Promise<IRoleEntity[]> { return this.api.searchRoles(state.roles.filter.query); } } ``` ###### Access group effects ```typescript import { EffectsService, IEffectsAction, } from 'redux-effects-promise'; import { BaseEffects, CustomActionBuilder, DiSupport, effectsBy, EffectsFactories, FormActionBuilder, ListActionBuilder, RouterActionBuilder, Selectors, } from 'react-application-core'; import { IPosAccessGroupEntity } from 'pos'; import { IApi } from '../../../api'; import { IPortalState } from '../../../app.interface'; import { PA_GROUP_SECTION } from './portal-access-group.interface'; import { PA_GROUPS_SECTION } from '../portal-access-groups.interface'; import { PortalRoutes } from '../../../app.routes'; @DiSupport.provideInSingleton(PortalAccessGroupEffects) @effectsBy( EffectsFactories.formSubmitErrorEffectsProxy(PA_GROUP_SECTION), EffectsFactories.succeedEditedListEffectsProxy({ listSection: PA_GROUPS_SECTION, formSection: PA_GROUP_SECTION, }) ) class PortalAccessGroupEffects extends BaseEffects<IApi> { /** * @stable [01.08.2020] * @param action */ @EffectsService.effects(FormActionBuilder.buildSubmitActionType(PA_GROUP_SECTION)) public $onSaveAccessGroup = (action: IEffectsAction): Promise<IPosAccessGroupEntity> => this.api.saveAccessGroup(action.data) /** * @stable [01.08.2020] * @param action * @param state */ @EffectsService.effects(CustomActionBuilder.buildCustomDeleteActionType(PA_GROUP_SECTION)) public async $onDeleteAccessGroup(action: IEffectsAction, state: IPortalState): Promise<IEffectsAction[]> { await this.api.deleteAccessGroup( Selectors.listSelectedEntity<IPosAccessGroupEntity>(state.access.accessGroups).id ); return [ ListActionBuilder.buildLoadAction(PA_GROUPS_SECTION), RouterActionBuilder.buildReplaceAction(PortalRoutes.ACCESS_GROUPS) ]; } } ``` ## License Licensed under MIT.