add users table, user form, show\create\delete user, router service
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
import React, {memo} from 'react';
|
import React, {memo} from 'react';
|
||||||
import {Route, Switch} from 'react-router-dom';
|
import {Route, Switch} from 'react-router-dom';
|
||||||
import {createStore} from '@reatom/core';
|
|
||||||
import {context} from '@reatom/react';
|
import {context} from '@reatom/react';
|
||||||
import mainPageRouter from '_pages/main/routing';
|
import mainPageRouter from '_pages/main/routing';
|
||||||
import usersPageRouter from '_pages/users/routing';
|
import usersPageRouter from '_pages/users/routing';
|
||||||
@ -12,6 +11,8 @@ import NotFoundPage from '_pages/not-found/components/page';
|
|||||||
import MainLayout from '../main-layout';
|
import MainLayout from '../main-layout';
|
||||||
import jss from 'jss';
|
import jss from 'jss';
|
||||||
import preset from 'jss-preset-default';
|
import preset from 'jss-preset-default';
|
||||||
|
import {store} from '../../../core/infrastructure/atom/store';
|
||||||
|
import ConnectedRouter from '../../../core/blocks/connected-router/ConnectedRouter';
|
||||||
|
|
||||||
jss.setup(preset());
|
jss.setup(preset());
|
||||||
|
|
||||||
@ -33,23 +34,23 @@ const styles = {
|
|||||||
jss.createStyleSheet(styles).attach();
|
jss.createStyleSheet(styles).attach();
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page: React.FC = () => {
|
||||||
const store = createStore();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<context.Provider value={store}>
|
<context.Provider value={store}>
|
||||||
<MainLayout>
|
<ConnectedRouter>
|
||||||
<Switch>
|
<MainLayout>
|
||||||
{mainPageRouter}
|
<Switch>
|
||||||
{usersPageRouter}
|
{mainPageRouter}
|
||||||
{actionsPageRouter}
|
{usersPageRouter}
|
||||||
{conditionsPageRouter}
|
{actionsPageRouter}
|
||||||
{graphsPageRouter}
|
{conditionsPageRouter}
|
||||||
{currenciesPageRouter}
|
{graphsPageRouter}
|
||||||
<Route>
|
{currenciesPageRouter}
|
||||||
<NotFoundPage />
|
<Route>
|
||||||
</Route>
|
<NotFoundPage />
|
||||||
</Switch>
|
</Route>
|
||||||
</MainLayout>
|
</Switch>
|
||||||
|
</MainLayout>
|
||||||
|
</ConnectedRouter>
|
||||||
</context.Provider>
|
</context.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type RequestEntities<T> = {
|
|||||||
type EntityWithId<T> = T & {
|
type EntityWithId<T> = T & {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
type EntityWithoutId<T> = Omit<T, 'id'>;
|
||||||
|
|
||||||
type ResponseEntities<T> = {
|
type ResponseEntities<T> = {
|
||||||
data: EntityWithId<T>[];
|
data: EntityWithId<T>[];
|
||||||
@ -63,8 +64,8 @@ export class CrudAPI<T> {
|
|||||||
return http.get<never, T>(`${this.endpoint}/${id}`);
|
return http.get<never, T>(`${this.endpoint}/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
create = (entity: T): Promise<EntityWithId<T>> => {
|
create = (entity: EntityWithoutId<T>): Promise<EntityWithId<T>> => {
|
||||||
return http.post<never, T, EntityWithId<T>>(this.endpoint, undefined, entity);
|
return http.post<never, EntityWithoutId<T>, EntityWithId<T>>(this.endpoint, undefined, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
update = (id: string, entity: T): Promise<EntityWithId<T>> => {
|
update = (id: string, entity: T): Promise<EntityWithId<T>> => {
|
||||||
|
|||||||
22
src/core/blocks/connected-router/ConnectedRouter.tsx
Normal file
22
src/core/blocks/connected-router/ConnectedRouter.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {useAtom} from '@reatom/react';
|
||||||
|
import React, {FC, Fragment, memo, PropsWithChildren, useEffect} from 'react';
|
||||||
|
import {useHistory} from 'react-router';
|
||||||
|
import {routerAtom} from '../../infrastructure/atom/routerAtom';
|
||||||
|
|
||||||
|
const ConnectedRouter: FC<PropsWithChildren<unknown>> = ({children}) => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const route = useAtom(routerAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
history.push(route);
|
||||||
|
}, [history, route]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{children}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ConnectedRouter);
|
||||||
1
src/core/blocks/connected-router/index.ts
Normal file
1
src/core/blocks/connected-router/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './ConnectedRouter';
|
||||||
@ -8,5 +8,9 @@ export const ROUTES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ENDPOINT = {
|
export const ENDPOINT = {
|
||||||
USERS: 'http://vigdorov.ru:3011/users',
|
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',
|
||||||
};
|
};
|
||||||
|
|||||||
17
src/core/infrastructure/atom/routerAtom.ts
Normal file
17
src/core/infrastructure/atom/routerAtom.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import {declareAction, declareAtom} from '@reatom/core';
|
||||||
|
import {createHashHistory} from 'history';
|
||||||
|
import {store} from './store';
|
||||||
|
|
||||||
|
const {location: {pathname, search}} = createHashHistory();
|
||||||
|
|
||||||
|
const routerAction = declareAction<string>();
|
||||||
|
|
||||||
|
export const routerAtom = declareAtom(`${pathname}${search}`, on => [
|
||||||
|
on(routerAction, (_state, payload) => payload),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const bindedActions = {
|
||||||
|
routerAction: (route: string) => {
|
||||||
|
store.dispatch(routerAction(route));
|
||||||
|
},
|
||||||
|
};
|
||||||
3
src/core/infrastructure/atom/store.ts
Normal file
3
src/core/infrastructure/atom/store.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import {createStore} from '@reatom/core';
|
||||||
|
|
||||||
|
export const store = createStore();
|
||||||
38
src/core/infrastructure/atom/usersAtom.ts
Normal file
38
src/core/infrastructure/atom/usersAtom.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {declareAction, declareAtom} from '@reatom/core';
|
||||||
|
import {User} from '../../../pages/users/types';
|
||||||
|
import {store} from './store';
|
||||||
|
|
||||||
|
export type FieldData = {
|
||||||
|
name: keyof User | Array<keyof User>;
|
||||||
|
value?: any;
|
||||||
|
touched?: boolean;
|
||||||
|
validating?: boolean;
|
||||||
|
errors?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const INIT_USERS: User[] = [];
|
||||||
|
export const INIT_USER: FieldData[] = [
|
||||||
|
{name: 'id', value: ''},
|
||||||
|
{name: 'login', value: ''},
|
||||||
|
{name: 'password', value: ''},
|
||||||
|
];
|
||||||
|
export const loadUsersAction = declareAction<User[]>();
|
||||||
|
|
||||||
|
export const usersAtom = declareAtom(INIT_USERS, on => [
|
||||||
|
on(loadUsersAction, (_state, payload) => payload),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const loadUserForm = declareAction<FieldData[]>();
|
||||||
|
|
||||||
|
export const userFormAtom = declareAtom(INIT_USER, on => [
|
||||||
|
on(loadUserForm, (_state, payload) => payload),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const bindedActions = {
|
||||||
|
loadUsersAction: (users: User[]) => {
|
||||||
|
store.dispatch(loadUsersAction(users));
|
||||||
|
},
|
||||||
|
loadUserForm: (fieldData: FieldData[]) => {
|
||||||
|
store.dispatch(loadUserForm(fieldData));
|
||||||
|
}
|
||||||
|
};
|
||||||
34
src/core/services/RouterService.ts
Normal file
34
src/core/services/RouterService.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {decode, encode, ParsedUrlQuery} from 'querystring';
|
||||||
|
import {bindedActions} from '../infrastructure/atom/routerAtom';
|
||||||
|
import {isNotEmpty} from '../referers/common';
|
||||||
|
|
||||||
|
type PushQueryOptions = {
|
||||||
|
reset?: boolean;
|
||||||
|
shouldRefresh?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQuery = () => decode(window.location.search.slice(1));
|
||||||
|
|
||||||
|
class RouterService {
|
||||||
|
push(route: string) {
|
||||||
|
bindedActions.routerAction(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushWithQuery(path: string, query: ParsedUrlQuery, options?: PushQueryOptions) {
|
||||||
|
const currentQuery = getQuery();
|
||||||
|
|
||||||
|
const finalQuery = encode({
|
||||||
|
...(!options?.reset ? {
|
||||||
|
...currentQuery,
|
||||||
|
} : {}),
|
||||||
|
...(options?.shouldRefresh ? {
|
||||||
|
__timestamp: Date.now(),
|
||||||
|
} : {}),
|
||||||
|
...query,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.push([path, finalQuery].filter(isNotEmpty).join('?'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routerService = new RouterService();
|
||||||
6
src/core/types/EntityModes.ts
Normal file
6
src/core/types/EntityModes.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum EntityMode {
|
||||||
|
Create = 'create',
|
||||||
|
Show = 'show',
|
||||||
|
Copy = 'copy',
|
||||||
|
Edit = 'edit',
|
||||||
|
}
|
||||||
4
src/pages/actions/api/ActionsAPI.ts
Normal file
4
src/pages/actions/api/ActionsAPI.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import {ENDPOINT} from '_consts/common';
|
||||||
|
import {CrudAPI} from '../../../core/api/CrudAPI';
|
||||||
|
|
||||||
|
export const actionsAPI = new CrudAPI(ENDPOINT.ACTIONS);
|
||||||
@ -1,4 +1,7 @@
|
|||||||
import React, {FC, memo} from 'react';
|
import React, {FC, memo} from 'react';
|
||||||
|
import {actionsAPI} from '../../api/ActionsAPI';
|
||||||
|
|
||||||
|
actionsAPI.request();
|
||||||
|
|
||||||
const Page: FC = () => {
|
const Page: FC = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {ENDPOINT} from '_consts/common';
|
import {ENDPOINT} from '_consts/common';
|
||||||
import {CrudAPI} from '../../../core/api/CrudAPI';
|
import {CrudAPI} from '../../../core/api/CrudAPI';
|
||||||
|
import {User} from '../types';
|
||||||
|
|
||||||
export const usersAPI = new CrudAPI(ENDPOINT.USERS);
|
export const usersAPI = new CrudAPI<User>(ENDPOINT.USERS);
|
||||||
|
|||||||
@ -1,11 +1,41 @@
|
|||||||
|
import {Button, Layout} from 'antd';
|
||||||
import React, {FC, memo} from 'react';
|
import React, {FC, memo} from 'react';
|
||||||
import {usersAPI} from '../../api/UsersAPI';
|
import {createUseStyles} from 'react-jss';
|
||||||
|
import {ROUTES} from '../../../../core/consts/common';
|
||||||
|
import {routerService} from '../../../../core/services/RouterService';
|
||||||
|
import {EntityMode} from '../../../../core/types/EntityModes';
|
||||||
|
import UserSidebar from '../user-sidebar/UserSidebar';
|
||||||
|
import UsersTable from '../users-table/UsersTable';
|
||||||
|
|
||||||
usersAPI.request();
|
const useStyles = createUseStyles({
|
||||||
|
header: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClickNewUser = () => {
|
||||||
|
routerService.pushWithQuery(ROUTES.USERS, {
|
||||||
|
mode: EntityMode.Create,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const Page: FC = () => {
|
const Page: FC = () => {
|
||||||
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<div>users</div>
|
<Layout>
|
||||||
|
<Layout.Header className={classes.header}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleClickNewUser}
|
||||||
|
>
|
||||||
|
New user
|
||||||
|
</Button>
|
||||||
|
</Layout.Header>
|
||||||
|
<Layout.Content>
|
||||||
|
<UsersTable />
|
||||||
|
<UserSidebar />
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
201
src/pages/users/components/user-sidebar/UserSidebar.tsx
Normal file
201
src/pages/users/components/user-sidebar/UserSidebar.tsx
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import {useAtom} from '@reatom/react';
|
||||||
|
import {Button, Drawer, Form, Input} from 'antd';
|
||||||
|
import React, {FC, Fragment, memo, useCallback, useEffect, useMemo} from 'react';
|
||||||
|
import {createUseStyles} from 'react-jss';
|
||||||
|
import {ROUTES} from '../../../../core/consts/common';
|
||||||
|
import {useQuery} from '../../../../core/hooks/useQuery';
|
||||||
|
import {bindedActions, FieldData, userFormAtom} from '../../../../core/infrastructure/atom/usersAtom';
|
||||||
|
import {isNotEmpty} from '../../../../core/referers/common';
|
||||||
|
import {routerService} from '../../../../core/services/RouterService';
|
||||||
|
import {EntityMode} from '../../../../core/types/EntityModes';
|
||||||
|
import {usersService} from '../../services/UsersServices';
|
||||||
|
import {User} from '../../types';
|
||||||
|
import {queryParsers} from '../../utils';
|
||||||
|
|
||||||
|
const AVAILABLE_CLOSE_MODES = [EntityMode.Show];
|
||||||
|
const DISABLED_FORM_MODES = [EntityMode.Show];
|
||||||
|
const SHOW_ID_MODES = [EntityMode.Show];
|
||||||
|
|
||||||
|
const useStyles = createUseStyles({
|
||||||
|
button: {
|
||||||
|
marginRight: '8px',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
routerService.push(ROUTES.USERS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFieldsChange = (_: FieldData[], allFields: FieldData[]) => {
|
||||||
|
bindedActions.loadUserForm(allFields);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserSidebar: FC = () => {
|
||||||
|
const {mode, id} = useQuery(queryParsers);
|
||||||
|
const fields = useAtom(userFormAtom);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
usersService.loadUser(id);
|
||||||
|
}, [id, mode]);
|
||||||
|
|
||||||
|
const disabled = useMemo(() => !mode || DISABLED_FORM_MODES.includes(mode), [mode]);
|
||||||
|
|
||||||
|
const handleCreate = useCallback(() => {
|
||||||
|
const user = fields.reduce<User>((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 (
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
onClick={handleCreate}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
case EntityMode.Edit:
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
case EntityMode.Show:
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
onClick={handleEdit}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [mode, classes, handleEdit, handleCreate]);
|
||||||
|
|
||||||
|
const renderFooter = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{primaryButton}
|
||||||
|
{mode === EntityMode.Show && (
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
onClick={handleCopy}
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
<Button onClick={handleClose}>Cancel</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [primaryButton, mode, classes, handleCopy, handleDelete]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
visible={!!mode}
|
||||||
|
closable={false}
|
||||||
|
onClose={handleBackdrop}
|
||||||
|
width="600"
|
||||||
|
title={title}
|
||||||
|
footer={renderFooter}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
fields={fields}
|
||||||
|
onFieldsChange={onFieldsChange as any}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="Login"
|
||||||
|
name="login"
|
||||||
|
>
|
||||||
|
<Input disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Password"
|
||||||
|
name="password"
|
||||||
|
>
|
||||||
|
<Input disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
{mode && SHOW_ID_MODES.includes(mode) && (
|
||||||
|
<Form.Item
|
||||||
|
label="ID"
|
||||||
|
name="id"
|
||||||
|
>
|
||||||
|
<Input disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(UserSidebar);
|
||||||
1
src/pages/users/components/user-sidebar/index.ts
Normal file
1
src/pages/users/components/user-sidebar/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './UserSidebar';
|
||||||
57
src/pages/users/components/users-table/UsersTable.tsx
Normal file
57
src/pages/users/components/users-table/UsersTable.tsx
Normal file
@ -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 (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
onRow={onRow}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(UsersTable);
|
||||||
1
src/pages/users/components/users-table/index.ts
Normal file
1
src/pages/users/components/users-table/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './UsersTable';
|
||||||
56
src/pages/users/services/UsersServices.ts
Normal file
56
src/pages/users/services/UsersServices.ts
Normal file
@ -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<FieldData[]>((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();
|
||||||
12
src/pages/users/types.ts
Normal file
12
src/pages/users/types.ts
Normal file
@ -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;
|
||||||
|
};
|
||||||
8
src/pages/users/utils.ts
Normal file
8
src/pages/users/utils.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {QueryParsers} from '../../core/utils/getQueryFromUrl';
|
||||||
|
import {stringParser} from '../../core/utils/queryParsers';
|
||||||
|
import {QueryParams} from './types';
|
||||||
|
|
||||||
|
export const queryParsers: QueryParsers<QueryParams> = {
|
||||||
|
id: stringParser(),
|
||||||
|
mode: stringParser(),
|
||||||
|
};
|
||||||
@ -26,6 +26,19 @@ module.exports = {
|
|||||||
compress: true,
|
compress: true,
|
||||||
open: true,
|
open: true,
|
||||||
port: 3189,
|
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: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
|||||||
Reference in New Issue
Block a user