diff --git a/src/core/api/CrudAPI.ts b/src/core/api/CrudAPI.ts index ccaf9ed..5602180 100644 --- a/src/core/api/CrudAPI.ts +++ b/src/core/api/CrudAPI.ts @@ -60,8 +60,8 @@ export class CrudAPI { return http.get>([this.endpoint, query.join('&')].filter(isNotEmpty).join('?')); } - find = (id: string): Promise => { - return http.get(`${this.endpoint}/${id}`); + find = (id: string): Promise> => { + return http.get>(`${this.endpoint}/${id}`); } create = (entity: EntityWithoutId): Promise> => { diff --git a/src/core/blocks/entity-table/EntityTable.tsx b/src/core/blocks/entity-table/EntityTable.tsx new file mode 100644 index 0000000..8e3a0ef --- /dev/null +++ b/src/core/blocks/entity-table/EntityTable.tsx @@ -0,0 +1,64 @@ +import {Atom} from '@reatom/core'; +import {useAtom} from '@reatom/react'; +import {head} from 'lodash'; +import Table, {ColumnsType} from 'antd/lib/table'; +import React, {FC, memo, useCallback, useEffect, useMemo} from 'react'; +import {CrudService} from '../../services/CrudService'; +import {EntityMode} from '../../types/EntityModes'; +import {EntityWithId} from '../../api/CrudAPI'; + +type Props = { + entityListAtom: Atom; + service: CrudService; +}; + +export const createEntityTable = function > ({ + entityListAtom, + service, +}: Props): FC { + return memo(() => { + const entityList = useAtom(entityListAtom); + + useEffect(() => { + service.loadEntityList(); + }, []); + + const onRow = useCallback((entity: EntityWithId) => ({ + onClick: () => { + service.navigate(EntityMode.Show, entity.id); + }, + }), []); + + const columns: ColumnsType> = useMemo(() => { + const entity = head(entityList); + if (entity) { + return Object.keys(entity).map(field => { + return { + title: field, + dataIndex: field, + key: field, + }; + }); + } + return []; + }, [entityList]); + + const dataSource = useMemo(() => { + return entityList.map(entity => { + return { + ...entity, + key: entity.id, + }; + }); + }, [entityList]); + + return ( + + ); + }); +}; + diff --git a/src/core/blocks/entity-table/index.ts b/src/core/blocks/entity-table/index.ts new file mode 100644 index 0000000..5c140ab --- /dev/null +++ b/src/core/blocks/entity-table/index.ts @@ -0,0 +1 @@ +export * from './EntityTable'; diff --git a/src/core/dts/comon.d.ts b/src/core/dts/comon.d.ts index c832333..2c36906 100644 --- a/src/core/dts/comon.d.ts +++ b/src/core/dts/comon.d.ts @@ -1,3 +1,5 @@ type Undefinable = T | undefined; type Nullable = T | undefined | null; + +type Indexed = Record; diff --git a/src/core/infrastructure/atom/createEntityAtoms.ts b/src/core/infrastructure/atom/createEntityAtoms.ts new file mode 100644 index 0000000..e3c5e70 --- /dev/null +++ b/src/core/infrastructure/atom/createEntityAtoms.ts @@ -0,0 +1,28 @@ +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 loadEntityList = declareAction(); + const loadEntityForm = declareAction(); + + const entityListAtom = declareAtom(INIT_ENTITY_LIST, on => [ + on(loadEntityList, (_state, payload) => payload) + ]); + const entityFormAtom = declareAtom(initEntity, on => [ + on(loadEntityForm, (_state, payload) => payload) + ]); + + const bindedActions = { + loadEntityList: (entities: typeof INIT_ENTITY_LIST) => { + store.dispatch(loadEntityList(entities)); + }, + loadEntityForm: (entity: T) => { + store.dispatch(loadEntityForm(entity)); + }, + }; + + return {entityListAtom, entityFormAtom, bindedActions}; +}; diff --git a/src/core/infrastructure/atom/usersAtom.ts b/src/core/infrastructure/atom/usersAtom.ts index d327b80..6a9a10f 100644 --- a/src/core/infrastructure/atom/usersAtom.ts +++ b/src/core/infrastructure/atom/usersAtom.ts @@ -2,14 +2,6 @@ import {declareAction, declareAtom} from '@reatom/core'; import {User} from '../../../pages/users/types'; import {store} from './store'; -export type FieldData = { - name: keyof User | Array; - value?: any; - touched?: boolean; - validating?: boolean; - errors?: string[]; -}; - const INIT_USERS: User[] = []; export const INIT_USER: User = { id: '', diff --git a/src/core/services/CrudService.ts b/src/core/services/CrudService.ts new file mode 100644 index 0000000..1112171 --- /dev/null +++ b/src/core/services/CrudService.ts @@ -0,0 +1,70 @@ +import {CrudAPI, EntityWithId, EntityWithoutId} from '../api/CrudAPI'; +import {EntityMode} from '../types/EntityModes'; +import {routerService} from './RouterService'; + +type Actions = { + loadEntityList: (entities: T[]) => void, + loadEntityForm: (entity: T) => void; +}; + +export class CrudService { + api: CrudAPI; + + actions: Actions; + + route: string; + + constructor(route: string, endpoint: string, actions: Actions) { + this.api = new CrudAPI(endpoint); + this.actions = actions; + this.route = route; + } + + loadEntityList() { + return this.api + .request() + .then(({data}) => { + this.actions.loadEntityList(data); + }); + } + + loadEntity(id?: string) { + if (id) { + this.api + .find(id) + .then(entity => { + this.actions.loadEntityForm(entity); + }); + } + } + + updateEntity({id, ...entity}: EntityWithId) { + this.api + .update(id, entity) + .then(this.goRootAndReload); + } + + removeEntity(id?: string) { + if (id) { + this.api + .remove(id) + .then(this.goRootAndReload); + } + } + + createEntity(entity: EntityWithoutId) { + this.api + .create(entity) + .then(this.goRootAndReload); + } + + goRootAndReload = () => { + this.loadEntityList().then(() => { + this.navigate(); + }); + } + + navigate(mode?: EntityMode, id?: string) { + routerService.pushWithQuery(this.route, {mode, id}, {reset: true}); + } +} diff --git a/src/core/services/RouterService.ts b/src/core/services/RouterService.ts index 994fa37..9cdcc26 100644 --- a/src/core/services/RouterService.ts +++ b/src/core/services/RouterService.ts @@ -1,6 +1,7 @@ import {decode, encode, ParsedUrlQuery} from 'querystring'; import {bindedActions} from '../infrastructure/atom/routerAtom'; import {isNotEmpty} from '../referers/common'; +import {objectEntries} from '../utils/objectEntries'; type PushQueryOptions = { reset?: boolean; @@ -14,7 +15,7 @@ class RouterService { bindedActions.routerAction(route); } - pushWithQuery(path: string, query: ParsedUrlQuery, options?: PushQueryOptions) { + pushWithQuery(path: string, query: Indexed>, options?: PushQueryOptions) { const currentQuery = getQuery(); const finalQuery = encode({ @@ -24,7 +25,12 @@ class RouterService { ...(options?.shouldRefresh ? { __timestamp: Date.now(), } : {}), - ...query, + ...objectEntries(query).reduce((acc, [key, value]) => { + if (value) { + acc[key] = value; + } + return acc; + }, {}), }); this.push([path, finalQuery].filter(isNotEmpty).join('?')); diff --git a/src/pages/graphs/components/page/Page.tsx b/src/pages/graphs/components/page/Page.tsx index c000f84..82c53c7 100644 --- a/src/pages/graphs/components/page/Page.tsx +++ b/src/pages/graphs/components/page/Page.tsx @@ -1,8 +1,51 @@ +import {Button, Layout} from 'antd'; import React, {FC, memo} from 'react'; +import {createUseStyles} from 'react-jss'; +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 {GraphModel} from '../../types'; + +const {entityListAtom, bindedActions} = createEntityAtoms({ + type: '', + graphName: '', + from: '', + to: '', +}); + +const service = new CrudService(ROUTES.GRAPHS, ENDPOINT.GRAPHS, bindedActions); + +const EntityTable = createEntityTable({entityListAtom, service}); + +const useStyles = createUseStyles({ + header: { + backgroundColor: '#fff', + } +}); + +const handleClickNewEntity = () => { + service.navigate(EntityMode.Create); +}; const Page: FC = () => { + const classes = useStyles(); + return ( -
graphs
+ + + + + + + + ); }; diff --git a/src/pages/graphs/types.ts b/src/pages/graphs/types.ts new file mode 100644 index 0000000..37265e9 --- /dev/null +++ b/src/pages/graphs/types.ts @@ -0,0 +1,8 @@ +type Graph = { + type: string; + graphName: string; + from: number; + to: number; +}; + +export type GraphModel = Record; diff --git a/src/pages/users/api/UsersAPI.ts b/src/pages/users/api/UsersAPI.ts index 135708b..6460af1 100644 --- a/src/pages/users/api/UsersAPI.ts +++ b/src/pages/users/api/UsersAPI.ts @@ -1,6 +1,6 @@ import {ENDPOINT} from '_consts/common'; -import {CrudAPI, EntityWithId, EntityWithoutId} from '../../../core/api/CrudAPI'; -import {http} from '../../../core/infrastructure/Http'; +import {CrudAPI, EntityWithId, EntityWithoutId} from '_api/CrudAPI'; +import {http} from '_infrastructure/Http'; import {User} from '../types'; import {ChangePasswordRequest} from './types'; diff --git a/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx b/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx index 4a2672e..ca3324a 100644 --- a/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx +++ b/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx @@ -1,8 +1,8 @@ import {Input, Modal} from 'antd'; import React, {FC, memo, useCallback, useState} from 'react'; -import {ROUTES} from '../../../../core/consts/common'; -import {useQuery} from '../../../../core/hooks/useQuery'; -import {routerService} from '../../../../core/services/RouterService'; +import {ROUTES} from '_consts/common'; +import {useQuery} from '_hooks/useQuery'; +import {routerService} from '_services/RouterService'; import {LABELS} from '../../consts'; import {ModalType} from '../../enums'; import {usersService} from '../../services/UsersServices'; diff --git a/src/pages/users/components/page/Page.tsx b/src/pages/users/components/page/Page.tsx index 90109a1..c02b5de 100644 --- a/src/pages/users/components/page/Page.tsx +++ b/src/pages/users/components/page/Page.tsx @@ -1,9 +1,9 @@ import {Button, Layout} from 'antd'; import React, {FC, Fragment, memo} from 'react'; import {createUseStyles} from 'react-jss'; -import {ROUTES} from '../../../../core/consts/common'; -import {routerService} from '../../../../core/services/RouterService'; -import {EntityMode} from '../../../../core/types/EntityModes'; +import {ROUTES} from '_consts/common'; +import {routerService} from '_services/RouterService'; +import {EntityMode} from '_types/EntityModes'; import ChangePasswordModal from '../change-password-modal/ChangePasswordModal'; import UserSidebar from '../user-sidebar/UserSidebar'; import UsersTable from '../users-table/UsersTable'; diff --git a/src/pages/users/components/user-sidebar/UserSidebar.tsx b/src/pages/users/components/user-sidebar/UserSidebar.tsx index 9f0e95e..943ba4f 100644 --- a/src/pages/users/components/user-sidebar/UserSidebar.tsx +++ b/src/pages/users/components/user-sidebar/UserSidebar.tsx @@ -2,11 +2,11 @@ import {useAtom} from '@reatom/react'; import {Button, Drawer, Input} from 'antd'; import React, {FC, Fragment, memo, SyntheticEvent, useCallback, useEffect, useMemo} from 'react'; import {createUseStyles} from 'react-jss'; -import {ROUTES} from '../../../../core/consts/common'; -import {useQuery} from '../../../../core/hooks/useQuery'; -import {bindedActions, userFormAtom} from '../../../../core/infrastructure/atom/usersAtom'; -import {routerService} from '../../../../core/services/RouterService'; -import {EntityMode} from '../../../../core/types/EntityModes'; +import {ROUTES} from '_consts/common'; +import {useQuery} from '_hooks/useQuery'; +import {bindedActions, userFormAtom} from '_infrastructure/atom/usersAtom'; +import {routerService} from '_services/RouterService'; +import {EntityMode} from '_types/EntityModes'; import {usersService} from '../../services/UsersServices'; import {queryParsers} from '../../utils'; diff --git a/src/pages/users/components/users-table/UsersTable.tsx b/src/pages/users/components/users-table/UsersTable.tsx index 4939cc7..81749de 100644 --- a/src/pages/users/components/users-table/UsersTable.tsx +++ b/src/pages/users/components/users-table/UsersTable.tsx @@ -3,11 +3,11 @@ import {Button, Table} from 'antd'; import {ColumnsType} from 'antd/lib/table'; import {head} from 'lodash'; import React, {FC, memo, useEffect, useMemo} from 'react'; -import {ROUTES} from '../../../../core/consts/common'; -import {usersAtom} from '../../../../core/infrastructure/atom/usersAtom'; -import {routerService} from '../../../../core/services/RouterService'; -import {EntityMode} from '../../../../core/types/EntityModes'; -import {objectKeys} from '../../../../core/utils/objectKeys'; +import {ROUTES} from '_consts/common'; +import {usersAtom} from '_infrastructure/atom/usersAtom'; +import {routerService} from '_services/RouterService'; +import {EntityMode} from '_types/EntityModes'; +import {objectKeys} from '_utils/objectKeys'; import {ModalType} from '../../enums'; import {usersService} from '../../services/UsersServices'; import {User} from '../../types'; @@ -37,23 +37,26 @@ const UsersTable: FC = () => { }, []); const columns: ColumnsType = useMemo(() => { - const user = head(users) ?? {} as User; - return objectKeys(user).map(field => { - if (field === 'password') { + const user = head(users); + if (user) { + return objectKeys(user).map(field => { + if (field === 'password') { + return { + title: field, + key: field, + render: () => ( + + ), + }; + } return { title: field, + dataIndex: field, key: field, - render: () => ( - - ), }; - } - return { - title: field, - dataIndex: field, - key: field, - }; - }); + }); + } + return []; }, [users]); const dataSource = useMemo(() => { diff --git a/src/pages/users/services/UsersServices.ts b/src/pages/users/services/UsersServices.ts index 1e4a041..72d75b0 100644 --- a/src/pages/users/services/UsersServices.ts +++ b/src/pages/users/services/UsersServices.ts @@ -1,7 +1,7 @@ -import {EntityWithoutId} from '../../../core/api/CrudAPI'; -import {ROUTES} from '../../../core/consts/common'; -import {bindedActions, INIT_USER} from '../../../core/infrastructure/atom/usersAtom'; -import {routerService} from '../../../core/services/RouterService'; +import {EntityWithoutId} from '_api/CrudAPI'; +import {ROUTES} from '_consts/common'; +import {bindedActions, INIT_USER} from '_infrastructure/atom/usersAtom'; +import {routerService} from '_services/RouterService'; import {usersAPI} from '../api/UsersAPI'; import {User} from '../types'; diff --git a/src/pages/users/types.ts b/src/pages/users/types.ts index 92817b5..38fe852 100644 --- a/src/pages/users/types.ts +++ b/src/pages/users/types.ts @@ -1,14 +1,5 @@ -import {EntityMode} from '../../core/types/EntityModes'; -import {ModalType} from './enums'; - export type User = { id: string; login: string; password: string; }; - -export type QueryParams = { - id?: string; - mode?: EntityMode; - modalType?: ModalType; -}; diff --git a/src/pages/users/utils.ts b/src/pages/users/utils.ts index 58782d1..64a9166 100644 --- a/src/pages/users/utils.ts +++ b/src/pages/users/utils.ts @@ -1,5 +1,5 @@ -import {EntityMode} from '../../core/types/EntityModes'; -import {stringParser} from '../../core/utils/queryParsers'; +import {EntityMode} from '_types/EntityModes'; +import {stringParser} from '_utils/queryParsers'; import {ModalType} from './enums'; export const queryParsers = {