From 91b5ddcfaff9ee78cf8da66094cde9b549906500 Mon Sep 17 00:00:00 2001 From: Nikolay Vigdorov Date: Sat, 17 Jul 2021 18:43:21 +0300 Subject: [PATCH] add actions --- .eslintignore | 3 +- .../blocks/entity-sidebar/EntitySidebar.tsx | 266 ++++++++++++++++++ src/core/blocks/entity-sidebar/index.ts | 1 + src/core/blocks/entity-table/EntityTable.tsx | 8 +- src/core/consts/common.ts | 14 +- .../infrastructure/atom/createEntityAtoms.ts | 3 +- src/core/services/CrudService.ts | 4 + src/pages/actions/api/ActionsAPI.ts | 4 - src/pages/actions/components/page/Page.tsx | 69 ++++- src/pages/actions/types.ts | 7 + src/pages/conditions/components/page/Page.tsx | 49 +++- src/pages/conditions/types.ts | 3 + src/pages/currencies/components/page/Page.tsx | 49 +++- src/pages/currencies/types.ts | 5 + src/pages/graphs/components/page/Page.tsx | 14 +- webpack.config.js | 1 - 16 files changed, 475 insertions(+), 25 deletions(-) create mode 100644 src/core/blocks/entity-sidebar/EntitySidebar.tsx create mode 100644 src/core/blocks/entity-sidebar/index.ts delete mode 100644 src/pages/actions/api/ActionsAPI.ts create mode 100644 src/pages/actions/types.ts create mode 100644 src/pages/conditions/types.ts create mode 100644 src/pages/currencies/types.ts diff --git a/.eslintignore b/.eslintignore index 67eb2e3..fdfb914 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,4 +8,5 @@ webpack.config.js jest.config.js babel.config.js -/scripts \ No newline at end of file +/scripts +server.js \ No newline at end of file diff --git a/src/core/blocks/entity-sidebar/EntitySidebar.tsx b/src/core/blocks/entity-sidebar/EntitySidebar.tsx new file mode 100644 index 0000000..f23cfc5 --- /dev/null +++ b/src/core/blocks/entity-sidebar/EntitySidebar.tsx @@ -0,0 +1,266 @@ +import {Atom} from '@reatom/core'; +import {useAtom} from '@reatom/react'; +import {Button, Checkbox as CheckboxInput, Drawer, Input, Select as SelectInput} from 'antd'; +import {CheckboxChangeEvent} from 'antd/lib/checkbox'; +import {SelectValue} from 'antd/lib/select'; +import React, {FC, Fragment, memo, SyntheticEvent, useCallback, useEffect, useMemo} from 'react'; +import {createUseStyles} from 'react-jss'; +import {queryParsers} from '../../../pages/users/utils'; +import {useQuery} from '../../hooks/useQuery'; +import {CrudService} from '../../services/CrudService'; +import {EntityMode} from '../../types/EntityModes'; + +export enum FormInputType { + Text = 'text', + Checkbox = 'checkobx', + Select = 'select', +} + +type SelectOption = { + value: string; + label: string; +}; + +type FormOption = { + name: string; + label: string; + type?: FormInputType; + options?: SelectOption[]; + checkboxLabel?: string; +}; + +type Options = { + entityFormAtom: Atom; + service: CrudService; + formOptions: FormOption[]; + entityName: string; +}; + +const AVAILABLE_CLOSE_MODES = [EntityMode.Show]; +const DISABLED_FORM_MODES = [EntityMode.Show]; + +const useStyles = createUseStyles({ + button: { + marginRight: '8px', + }, + input: { + marginBottom: '16px', + width: '100%', + }, +}); + +export const createEntitySidebar = function ({ + entityFormAtom, + service, + formOptions, + entityName, +}: Options): FC { + return memo(() => { + const {mode, id} = useQuery(queryParsers); + const form = useAtom(entityFormAtom); + const classes = useStyles(); + + useEffect(() => { + service.loadEntity(id); + }, [id, mode]); + + const handleClose = () => { + service.navigate(); + }; + + const onChangeInput = (event: SyntheticEvent) => { + const {name, value} = event.currentTarget; + service.loadEntityForm({ + ...form, + [name]: value, + }); + }; + + const onChangeCheckbox = (event: CheckboxChangeEvent) => { + const {name, checked} = event.target; + service.loadEntityForm({ + ...form, + [name!]: checked, + }); + }; + + const onChangeSelect = (name: string) => (value: SelectValue) => { + if (!Array.isArray(value)) { + service.loadEntityForm({ + ...form, + [name]: value, + }); + } + }; + + const disabled = useMemo(() => !mode || DISABLED_FORM_MODES.includes(mode), [mode]); + + const handleCreateUser = useCallback(() => { + service.createEntity(form); + }, [form]); + + const handleSaveUser = useCallback(() => { + if (id) { + service.updateEntity({ + ...form, + id, + }); + } + }, [form, id]); + + const handleCopy = useCallback(() => { + service.navigate(EntityMode.Copy, id); + }, [id]); + + const handleEdit = useCallback(() => { + service.navigate(EntityMode.Edit, id); + }, [id]); + + const handleDelete = useCallback(() => { + service.removeEntity(id); + }, [id]); + + const handleBackdrop = useCallback(() => { + if (mode && AVAILABLE_CLOSE_MODES.includes(mode)) { + handleClose(); + } + }, [mode]); + + const title = useMemo(() => { + switch (mode) { + case EntityMode.Create: + return `Creating a ${entityName}`; + case EntityMode.Copy: + return `Coping ${entityName} "${id}"`; + case EntityMode.Edit: + return `Editing ${entityName} "${id}"`; + case EntityMode.Show: + return `Viewing ${entityName} "${id}"`; + default: + return `Mode "${mode}" not supported for ${entityName} form`; + } + }, [mode, id]); + + const primaryButton = useMemo(() => { + switch (mode) { + case EntityMode.Create: + case EntityMode.Copy: + return ( + + ); + case EntityMode.Edit: + return ( + + ); + case EntityMode.Show: + return ( + + ); + default: + return null; + } + }, [mode, classes, handleEdit, handleCreateUser, handleSaveUser]); + + const renderFooter = useMemo(() => { + return ( +
+ {primaryButton} + {mode === EntityMode.Show && ( + + + + + )} + +
+ ); + }, [primaryButton, mode, classes, handleCopy, handleDelete]); + + return ( + +
+ {formOptions.map(({name, label, type = FormInputType.Text, options, checkboxLabel}) => { + return ( +
+ + {type === FormInputType.Checkbox && ( + + {checkboxLabel ?? label} + + )} + {type === FormInputType.Select && ( + + {(options ?? []).map(option => ( + + {option.label} + + ))} + + )} + {type === FormInputType.Text && ( + + )} +
+ ); + })} +
+
+ ); + }); +}; diff --git a/src/core/blocks/entity-sidebar/index.ts b/src/core/blocks/entity-sidebar/index.ts new file mode 100644 index 0000000..8b9ee5c --- /dev/null +++ b/src/core/blocks/entity-sidebar/index.ts @@ -0,0 +1 @@ +export * from './EntitySidebar'; diff --git a/src/core/blocks/entity-table/EntityTable.tsx b/src/core/blocks/entity-table/EntityTable.tsx index 8e3a0ef..c944ff9 100644 --- a/src/core/blocks/entity-table/EntityTable.tsx +++ b/src/core/blocks/entity-table/EntityTable.tsx @@ -7,15 +7,15 @@ import {CrudService} from '../../services/CrudService'; import {EntityMode} from '../../types/EntityModes'; import {EntityWithId} from '../../api/CrudAPI'; -type Props = { +type Options = { entityListAtom: Atom; service: CrudService; }; -export const createEntityTable = function > ({ +export const createEntityTable = function ({ entityListAtom, service, -}: Props): FC { +}: Options): FC { return memo(() => { const entityList = useAtom(entityListAtom); @@ -44,7 +44,7 @@ export const createEntityTable = function > ({ }, [entityList]); const dataSource = useMemo(() => { - return entityList.map(entity => { + return entityList.map((entity: any) => { return { ...entity, key: entity.id, diff --git a/src/core/consts/common.ts b/src/core/consts/common.ts index 3fa1cba..4afc484 100644 --- a/src/core/consts/common.ts +++ b/src/core/consts/common.ts @@ -7,11 +7,13 @@ export const ROUTES = { CURRENCIES: '/currencies', }; +const PROTOCOL = location.protocol; + export const ENDPOINT = { - AUTH: 'https://localhost:3189/api/auth', - USERS: 'https://localhost:3189/api/users', - ACTIONS: 'https://localhost:3189/api/bot/actions', - CONDITIONS: 'https://localhost:3189/api/bot/conditions', - GRAPHS: 'https://localhost:3189/api/bot/graphs', - CURRENCIES: 'https://localhost:3189/api/bot/currencies', + AUTH: `${PROTOCOL}//localhost:3189/api/auth`, + USERS: `${PROTOCOL}//localhost:3189/api/users`, + ACTIONS: `${PROTOCOL}//localhost:3189/api/bot/actions`, + CONDITIONS: `${PROTOCOL}//localhost:3189/api/bot/conditions`, + GRAPHS: `${PROTOCOL}//localhost:3189/api/bot/graphs`, + CURRENCIES: `${PROTOCOL}//localhost:3189/api/bot/currencies`, }; diff --git a/src/core/infrastructure/atom/createEntityAtoms.ts b/src/core/infrastructure/atom/createEntityAtoms.ts index e3c5e70..3d4885a 100644 --- a/src/core/infrastructure/atom/createEntityAtoms.ts +++ b/src/core/infrastructure/atom/createEntityAtoms.ts @@ -1,9 +1,8 @@ import {declareAction, declareAtom} from '@reatom/core'; -import {EntityWithId} from '../../api/CrudAPI'; import {store} from './store'; export const createEntityAtoms = (initEntity: T) => { - const INIT_ENTITY_LIST: EntityWithId[] = []; + const INIT_ENTITY_LIST: T[] = []; const loadEntityList = declareAction(); const loadEntityForm = declareAction(); diff --git a/src/core/services/CrudService.ts b/src/core/services/CrudService.ts index 1112171..79aa32f 100644 --- a/src/core/services/CrudService.ts +++ b/src/core/services/CrudService.ts @@ -20,6 +20,10 @@ export class CrudService { this.route = route; } + loadEntityForm(form: T) { + this.actions.loadEntityForm(form); + } + loadEntityList() { return this.api .request() diff --git a/src/pages/actions/api/ActionsAPI.ts b/src/pages/actions/api/ActionsAPI.ts deleted file mode 100644 index 48fc5b4..0000000 --- a/src/pages/actions/api/ActionsAPI.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {ENDPOINT} from '_consts/common'; -import {CrudAPI} from '../../../core/api/CrudAPI'; - -export const actionsAPI = new CrudAPI(ENDPOINT.ACTIONS); diff --git a/src/pages/actions/components/page/Page.tsx b/src/pages/actions/components/page/Page.tsx index ac66ddf..0893c38 100644 --- a/src/pages/actions/components/page/Page.tsx +++ b/src/pages/actions/components/page/Page.tsx @@ -1,11 +1,74 @@ +import {Button, Layout} from 'antd'; +import moment from 'moment'; import React, {FC, memo} from 'react'; -import {actionsAPI} from '../../api/ActionsAPI'; +import {createUseStyles} from 'react-jss'; +import {createEntityAtoms} from '_infrastructure/atom/createEntityAtoms'; +import {createEntitySidebar, FormInputType} from '../../../../core/blocks/entity-sidebar'; +import {createEntityTable} from '../../../../core/blocks/entity-table'; +import {ENDPOINT, ROUTES} from '../../../../core/consts/common'; +import {CrudService} from '../../../../core/services/CrudService'; +import {EntityMode} from '../../../../core/types/EntityModes'; +import {ActionModel} from '../../types'; -actionsAPI.request(); +const {entityListAtom, entityFormAtom, bindedActions} = createEntityAtoms({ + createAt: moment().toISOString(), + closedAt: moment().toISOString(), + type: '', + login: '', + isExperiment: true, +}); + +const service = new CrudService(ROUTES.ACTIONS, ENDPOINT.ACTIONS, bindedActions); + +const TYPE_SELECT_OPTIONS = [ + {value: 'up', label: 'Up'}, + {value: 'down', label: 'Down'}, +]; + +const formOptions = [ + {name: 'createAt', label: 'Create at'}, + {name: 'closedAt', label: 'Close at'}, + {name: 'type', label: 'Type', type: FormInputType.Select, options: TYPE_SELECT_OPTIONS}, + {name: 'login', label: 'Login'}, + {name: 'isExperiment', label: 'Is experiment', type: FormInputType.Checkbox, checkboxLabel: 'Enabled experiment'}, +]; + +const EntityTable = createEntityTable({entityListAtom, service}); +const EntitySidebar = createEntitySidebar({ + entityFormAtom, + service, + formOptions, + entityName: 'Action' +}); + +const useStyles = createUseStyles({ + header: { + backgroundColor: '#fff', + } +}); + +const handleClickNewEntity = () => { + service.navigate(EntityMode.Create); +}; const Page: FC = () => { + const classes = useStyles(); + return ( -
actions
+ + + + + + + + + ); }; diff --git a/src/pages/actions/types.ts b/src/pages/actions/types.ts new file mode 100644 index 0000000..4048a1f --- /dev/null +++ b/src/pages/actions/types.ts @@ -0,0 +1,7 @@ +export type ActionModel = { + createAt: string; + closedAt: string; + type: string; + login: string; + isExperiment: boolean; +}; diff --git a/src/pages/conditions/components/page/Page.tsx b/src/pages/conditions/components/page/Page.tsx index 958ac65..82089c4 100644 --- a/src/pages/conditions/components/page/Page.tsx +++ b/src/pages/conditions/components/page/Page.tsx @@ -1,8 +1,55 @@ +import {Button, Layout} from 'antd'; import React, {FC, memo} from 'react'; +import {createUseStyles} from 'react-jss'; +import {createEntitySidebar} from '../../../../core/blocks/entity-sidebar'; +import {createEntityTable} from '../../../../core/blocks/entity-table'; +import {ENDPOINT, ROUTES} from '../../../../core/consts/common'; +import {createEntityAtoms} from '../../../../core/infrastructure/atom/createEntityAtoms'; +import {CrudService} from '../../../../core/services/CrudService'; +import {EntityMode} from '../../../../core/types/EntityModes'; +import {CurrencyModel} from '../../types'; + +const {entityListAtom, entityFormAtom, bindedActions} = createEntityAtoms({ + name: '', +}); + +const service = new CrudService(ROUTES.GRAPHS, ENDPOINT.GRAPHS, bindedActions); + +const formOptions = [ + {name: 'name', label: 'Name'}, +]; + +const EntityTable = createEntityTable({entityListAtom, service}); +const EntitySidebar = createEntitySidebar({entityFormAtom, service, formOptions, entityName: 'Currency'}); + +const useStyles = createUseStyles({ + header: { + backgroundColor: '#fff', + } +}); + +const handleClickNewEntity = () => { + service.navigate(EntityMode.Create); +}; const Page: FC = () => { + const classes = useStyles(); + return ( -
conditions
+ + + + + + + + + ); }; diff --git a/src/pages/conditions/types.ts b/src/pages/conditions/types.ts new file mode 100644 index 0000000..fd180e9 --- /dev/null +++ b/src/pages/conditions/types.ts @@ -0,0 +1,3 @@ +export type CurrencyModel = { + name: string; +}; diff --git a/src/pages/currencies/components/page/Page.tsx b/src/pages/currencies/components/page/Page.tsx index 018a4a8..27b371c 100644 --- a/src/pages/currencies/components/page/Page.tsx +++ b/src/pages/currencies/components/page/Page.tsx @@ -1,8 +1,55 @@ +import {Button, Layout} from 'antd'; import React, {FC, memo} from 'react'; +import {createUseStyles} from 'react-jss'; +import {createEntitySidebar} from '../../../../core/blocks/entity-sidebar'; +import {createEntityTable} from '../../../../core/blocks/entity-table'; +import {ENDPOINT, ROUTES} from '../../../../core/consts/common'; +import {createEntityAtoms} from '../../../../core/infrastructure/atom/createEntityAtoms'; +import {CrudService} from '../../../../core/services/CrudService'; +import {EntityMode} from '../../../../core/types/EntityModes'; +import {CurrencyModel} from '../../types'; + +const {entityListAtom, entityFormAtom, bindedActions} = createEntityAtoms({ + name: '', +}); + +const service = new CrudService(ROUTES.CURRENCIES, ENDPOINT.CURRENCIES, bindedActions); + +const formOptions = [ + {name: 'name', label: 'Name'}, +]; + +const EntityTable = createEntityTable({entityListAtom, service}); +const EntitySidebar = createEntitySidebar({entityFormAtom, service, formOptions, entityName: 'Currency'}); + +const useStyles = createUseStyles({ + header: { + backgroundColor: '#fff', + } +}); + +const handleClickNewEntity = () => { + service.navigate(EntityMode.Create); +}; const Page: FC = () => { + const classes = useStyles(); + return ( -
currencies
+ + + + + + + + + ); }; diff --git a/src/pages/currencies/types.ts b/src/pages/currencies/types.ts new file mode 100644 index 0000000..be36f22 --- /dev/null +++ b/src/pages/currencies/types.ts @@ -0,0 +1,5 @@ +type Currency = { + name: string; +}; + +export type CurrencyModel = Record; diff --git a/src/pages/graphs/components/page/Page.tsx b/src/pages/graphs/components/page/Page.tsx index 82c53c7..8ee3f81 100644 --- a/src/pages/graphs/components/page/Page.tsx +++ b/src/pages/graphs/components/page/Page.tsx @@ -1,6 +1,7 @@ import {Button, Layout} from 'antd'; import React, {FC, memo} from 'react'; import {createUseStyles} from 'react-jss'; +import {createEntitySidebar} from '../../../../core/blocks/entity-sidebar'; import {createEntityTable} from '../../../../core/blocks/entity-table'; import {ENDPOINT, ROUTES} from '../../../../core/consts/common'; import {createEntityAtoms} from '../../../../core/infrastructure/atom/createEntityAtoms'; @@ -8,16 +9,24 @@ import {CrudService} from '../../../../core/services/CrudService'; import {EntityMode} from '../../../../core/types/EntityModes'; import {GraphModel} from '../../types'; -const {entityListAtom, bindedActions} = createEntityAtoms({ +const {entityListAtom, entityFormAtom, bindedActions} = createEntityAtoms({ type: '', graphName: '', from: '', to: '', }); -const service = new CrudService(ROUTES.GRAPHS, ENDPOINT.GRAPHS, bindedActions); +const service = new CrudService(ROUTES.GRAPHS, ENDPOINT.GRAPHS, bindedActions); + +const formOptions = [ + {name: 'type', label: 'Type'}, + {name: 'graphName', label: 'Graph name'}, + {name: 'from', label: 'From'}, + {name: 'to', label: 'To'}, +]; const EntityTable = createEntityTable({entityListAtom, service}); +const EntitySidebar = createEntitySidebar({entityFormAtom, service, formOptions, entityName: 'Graph'}); const useStyles = createUseStyles({ header: { @@ -44,6 +53,7 @@ const Page: FC = () => { + ); diff --git a/webpack.config.js b/webpack.config.js index a87948e..7aa1ed7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -26,7 +26,6 @@ module.exports = { compress: true, open: true, port: 3189, - http2: true, proxy: { '/api/users': { target: 'http://vigdorov.ru:3011',