((acc, {name, value}) => {
+ if (Array.isArray(name)) {
+ if (isNotEmpty(name[0])) {
+ acc[name[0]] = value;
+ }
+ } else {
+ acc[name] = value;
+ }
+ return acc;
+ }, {id: '', login: '', password: ''});
+ usersService.createUser(user);
+ }, [fields]);
+
+ const handleCopy = useCallback(() => {
+ if (id) {
+ routerService.pushWithQuery(ROUTES.USERS, {
+ id,
+ mode: EntityMode.Copy,
+ });
+ }
+ }, [id]);
+
+ const handleEdit = useCallback(() => {
+ if (id) {
+ routerService.pushWithQuery(ROUTES.USERS, {
+ id,
+ mode: EntityMode.Edit,
+ });
+ }
+ }, [id]);
+
+ const handleDelete = useCallback(() => {
+ usersService.removeUser(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 user';
+ case EntityMode.Copy:
+ return `Coping user "${id}"`;
+ case EntityMode.Edit:
+ return `Editing user "${id}"`;
+ case EntityMode.Show:
+ return `Viewing user "${id}"`;
+ default:
+ return `Mode "${mode}" not supported for user 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, handleCreate]);
+
+ const renderFooter = useMemo(() => {
+ return (
+
+ {primaryButton}
+ {mode === EntityMode.Show && (
+
+
+
+
+ )}
+
+
+ );
+ }, [primaryButton, mode, classes, handleCopy, handleDelete]);
+
+ return (
+
+
+
+
+
+
+
+ {mode && SHOW_ID_MODES.includes(mode) && (
+
+
+
+ )}
+
+
+ );
+};
+
+export default memo(UserSidebar);
diff --git a/src/pages/users/components/user-sidebar/index.ts b/src/pages/users/components/user-sidebar/index.ts
new file mode 100644
index 0000000..6246a56
--- /dev/null
+++ b/src/pages/users/components/user-sidebar/index.ts
@@ -0,0 +1 @@
+export * from './UserSidebar';
diff --git a/src/pages/users/components/users-table/UsersTable.tsx b/src/pages/users/components/users-table/UsersTable.tsx
new file mode 100644
index 0000000..4de42f0
--- /dev/null
+++ b/src/pages/users/components/users-table/UsersTable.tsx
@@ -0,0 +1,57 @@
+import {useAtom} from '@reatom/react';
+import {Table} from 'antd';
+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 {usersService} from '../../services/UsersServices';
+import {User} from '../../types';
+
+const onRow = (user: User) => ({
+ onClick: () => {
+ routerService.pushWithQuery(ROUTES.USERS, {
+ id: user.id,
+ mode: EntityMode.Show,
+ });
+ },
+});
+
+const UsersTable: FC = () => {
+ const users = useAtom(usersAtom);
+
+ useEffect(() => {
+ usersService.loadUsers();
+ }, []);
+
+ const columns = useMemo(() => {
+ return objectKeys(head(users) ?? {}).map(field => {
+ return {
+ title: field,
+ dataIndex: field,
+ key: field,
+ };
+ });
+ }, [users]);
+
+ const dataSource = useMemo(() => {
+ return users.map(user => {
+ return {
+ ...user,
+ key: user.id,
+ };
+ });
+ }, [users]);
+
+ return (
+
+ );
+};
+
+export default memo(UsersTable);
diff --git a/src/pages/users/components/users-table/index.ts b/src/pages/users/components/users-table/index.ts
new file mode 100644
index 0000000..0cd9b9d
--- /dev/null
+++ b/src/pages/users/components/users-table/index.ts
@@ -0,0 +1 @@
+export * from './UsersTable';
diff --git a/src/pages/users/services/UsersServices.ts b/src/pages/users/services/UsersServices.ts
new file mode 100644
index 0000000..6cb377b
--- /dev/null
+++ b/src/pages/users/services/UsersServices.ts
@@ -0,0 +1,56 @@
+import {ROUTES} from '../../../core/consts/common';
+import {bindedActions, FieldData, INIT_USER} from '../../../core/infrastructure/atom/usersAtom';
+import {routerService} from '../../../core/services/RouterService';
+import {objectEntries} from '../../../core/utils/objectEntries';
+import {usersAPI} from '../api/UsersAPI';
+import {User} from '../types';
+
+class UsersService {
+ loadUsers() {
+ return usersAPI
+ .request()
+ .then(({data}) => {
+ bindedActions.loadUsersAction(data);
+ });
+ }
+
+ loadUser(id?: string) {
+ if (id) {
+ usersAPI
+ .find(id)
+ .then(user => {
+ const fieldData = objectEntries(user).reduce((acc, [name, value]) => {
+ acc.push({name, value});
+ return acc;
+ }, []);
+ bindedActions.loadUserForm(fieldData);
+ });
+ }
+ bindedActions.loadUserForm(INIT_USER);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ createUser({id, ...user}: User) {
+ usersAPI
+ .create(user)
+ .then(() => {
+ this.loadUsers().then(() => {
+ routerService.push(ROUTES.USERS);
+ });
+ });
+ }
+
+ removeUser(id?: string) {
+ if (id) {
+ usersAPI
+ .remove(id)
+ .then(() => {
+ this.loadUsers().then(() => {
+ routerService.push(ROUTES.USERS);
+ });
+ });
+ }
+ }
+}
+
+export const usersService = new UsersService();
diff --git a/src/pages/users/types.ts b/src/pages/users/types.ts
new file mode 100644
index 0000000..9b4dd00
--- /dev/null
+++ b/src/pages/users/types.ts
@@ -0,0 +1,12 @@
+import {EntityMode} from '../../core/types/EntityModes';
+
+export type User = {
+ id: string;
+ login: string;
+ password: string;
+};
+
+export type QueryParams = {
+ id?: string;
+ mode?: EntityMode;
+};
diff --git a/src/pages/users/utils.ts b/src/pages/users/utils.ts
new file mode 100644
index 0000000..202631b
--- /dev/null
+++ b/src/pages/users/utils.ts
@@ -0,0 +1,8 @@
+import {QueryParsers} from '../../core/utils/getQueryFromUrl';
+import {stringParser} from '../../core/utils/queryParsers';
+import {QueryParams} from './types';
+
+export const queryParsers: QueryParsers = {
+ id: stringParser(),
+ mode: stringParser(),
+};
diff --git a/webpack.config.js b/webpack.config.js
index 44eb4cb..b7abd54 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -26,6 +26,19 @@ module.exports = {
compress: true,
open: true,
port: 3189,
+ http2: true,
+ proxy: {
+ '/api/users': {
+ target: 'http://vigdorov.ru:3011',
+ pathRewrite: { '^/api': '' },
+ secure: false,
+ },
+ '/api/bot': {
+ target: 'http://vigdorov.ru:3012',
+ pathRewrite: { '^/api/bot': '' },
+ secure: false,
+ },
+ },
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],