React Application Core
A react-based application core for the business applications.
Flow
Description
The library is designed to quickly start develop the business applications are based on React, Redux, Material-UI.
Demo
Dependencies
- react
- redux
- react-redux
- react-router-dom
- material-components-web
- ramda
- InversifyJS
- @dagrejs/graphlib
- moment
- axios
- store
- socket.io-client
- react-text-mask
Classes & markers
@action-builder
@component-container-impl
- FilterFormDialogContainer (01.08.2020)
- FormContainer (09.05.2020)
- FormTabPanelContainer (30.07.2020)
- PageToolbarContainer (31.07.2020)
- SearchToolbarContainer (31.07.2020)
- TabPanelContainer (30.07.2020)
- ToolbarToolsContainer (10.06.2020)
- UnsavedFormChangesDialogContainer (15.06.2020)
@component-impl
- Calendar (29.12.2020)
- Chart (18.12.2020)
- Chip (02.06.2020)
- ChipsField (16.06.2020)
- CronField (28.12.2020)
- Dialog & BaseDialog (10.10.2020)
- DnD (18.10.2020)
- Drawer (29.05.2020)
- FileField (21.08.2020)
- FormLayout (31.05.2020)
- GridHead (20.05.2020)
- Header (22.05.2020)
- ListItem (17.08.2020)
- Main (22.05.2020)
- PageToolbar (10.06.2020)
- SearchToolbar
- Slider (16.10.2020)
- SliderField (16.10.2020)
- SubHeader (22.05.2020)
- SubHeaderLink (22.05.2020)
- TextField (21.08.2020)
- Title (01.06.2020)
- ToolbarTools (01.08.2020)
- UnsavedFormChangesDialog (01.08.2020)
EffectsFactories
@effects-proxy-factory- makeDestroyedContainerEffectsProxy (09.09.2020)
- makeFilteredListEffectsProxy (09.09.2020)
- makeFilterFormDialogEffectsProxy (09.09.2020)
- makeLoadedListOnTabActivateEffectsProxy (22.09.2020)
- makeSucceedEditedListEffectsProxy (10.09.2020)
@reducer
@map
- map-as-component
- map-as-container
- map-as-original
- map-as-wrapper
- map-as
- map-container-as-component
- map-entity-as-container
- mapper
@middleware
@configuration-entity
@redux-holder-entity
Utils
- EntityUtils (23.01.2021)
- FnUtils (18.01.2021)
- MultiFieldUtils (29.08.2020)
- PageUtils (03.11.2020)
- ReducerUtils (23.12.2020)
- SelectOptionUtils (02.11.2020)
- SortUtils (14.10.2020)
- StorageUtils (04.09.2020)
- ValidatorUtils (22.01.2021)
Theme customization (styling)
Button
Chart
Chip
DefaultLayout
- _default-layout.scss (27.05.2020)
- _default-layout-constant.scss (27.05.2020)
- _default-layout-mixin.scss (27.05.2020)
Dialog
DnD
Drawer
Field
Form
FormLayout
- _form-layout.scss (31.05.2020)
- _form-layout-constant.scss (31.05.2020)
- _form-layout-mixin.scss (31.05.2020)
Header
Main
NavigationList
- _navigation-list.scss (15.05.2020)
- _navigation-list-constant.scss (15.05.2020)
- _navigation-list-mixin.scss (15.05.2020)
PageToolbar
- _page-toolbar.scss (10.06.2020)
- _page-toolbar-constant.scss (10.06.2020)
- _page-toolbar-mixin.scss (10.06.2020)
SubHeaderLink
- _sub-header-link.scss (22.05.2020)
- _sub-header-link-constant.scss (22.05.2020)
- _sub-header-link-mixin.scss (22.05.2020)
TabPanel
- _tab-panel.scss (19.05.2020)
- _tab-panel-constant.scss (19.05.2020)
- _tab-panel-mixin.scss (19.05.2020)
TextField
Title
Usage
Containers
Roles container
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
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
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
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.