add crud api
This commit is contained in:
28
src/app/components/main-layout/MainLayout.tsx
Normal file
28
src/app/components/main-layout/MainLayout.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import {Layout} from 'antd';
|
||||
import React, {FC, memo, PropsWithChildren} from 'react';
|
||||
import {createUseStyles} from 'react-jss';
|
||||
import Menu from '../menu';
|
||||
|
||||
const useStyles = createUseStyles({
|
||||
layout: {
|
||||
height: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
const MainLayout: FC<PropsWithChildren<unknown>> = ({children}) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Layout className={classes.layout}>
|
||||
<Layout.Sider>
|
||||
<Menu />
|
||||
</Layout.Sider>
|
||||
<Layout>
|
||||
<Layout.Content>
|
||||
{children}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MainLayout);
|
||||
1
src/app/components/main-layout/index.ts
Normal file
1
src/app/components/main-layout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './MainLayout';
|
||||
58
src/app/components/menu/Menu.tsx
Normal file
58
src/app/components/menu/Menu.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import {Menu} from 'antd';
|
||||
import React, {FC, memo, useCallback, useMemo, useState} from 'react';
|
||||
import {TeamOutlined, DollarCircleOutlined, ClusterOutlined, InteractionOutlined, DeploymentUnitOutlined} from '@ant-design/icons';
|
||||
import {ROUTES} from '_consts/common';
|
||||
import {Link, useLocation} from 'react-router-dom';
|
||||
|
||||
const MENU = [
|
||||
{
|
||||
label: 'Users',
|
||||
icon: <TeamOutlined />,
|
||||
url: ROUTES.USERS,
|
||||
},
|
||||
{
|
||||
label: 'Actions',
|
||||
icon: <InteractionOutlined />,
|
||||
url: ROUTES.ACTIONS,
|
||||
},
|
||||
{
|
||||
label: 'Conditions',
|
||||
icon: <ClusterOutlined />,
|
||||
url: ROUTES.CONDITIONS,
|
||||
},
|
||||
{
|
||||
label: 'Graphs',
|
||||
icon: <DeploymentUnitOutlined />,
|
||||
url: ROUTES.GRAPHS,
|
||||
},
|
||||
{
|
||||
label: 'Currencies',
|
||||
icon: <DollarCircleOutlined />,
|
||||
url: ROUTES.CURRENCIES,
|
||||
},
|
||||
];
|
||||
|
||||
const TopMenu: FC = () => {
|
||||
const {pathname} = useLocation();
|
||||
const [selected, setSelected] = useState(pathname);
|
||||
|
||||
const selectedKeys = useMemo(() => [selected], [selected]);
|
||||
|
||||
const handleClick = useCallback((e: {key: string}) => {
|
||||
setSelected(e.key);
|
||||
}, [setSelected]);
|
||||
|
||||
return (
|
||||
<Menu onClick={handleClick} selectedKeys={selectedKeys} theme="dark" mode="inline">
|
||||
{MENU.map(({label, icon, url}) => {
|
||||
return (
|
||||
<Menu.Item key={url} icon={icon}>
|
||||
<Link to={url}>{label}</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TopMenu);
|
||||
1
src/app/components/menu/index.ts
Normal file
1
src/app/components/menu/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './Menu';
|
||||
@ -1,17 +0,0 @@
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -1,41 +1,56 @@
|
||||
import React, {Fragment, memo} from 'react';
|
||||
import React, {memo} from 'react';
|
||||
import {Route, Switch} from 'react-router-dom';
|
||||
import {Container, createStyles, makeStyles} from '@material-ui/core';
|
||||
import {createStore} from '@reatom/core';
|
||||
import {context} from '@reatom/react';
|
||||
import mainPageRouter from '_pages/main/routing';
|
||||
import usersPageRouter from '_pages/users/routing';
|
||||
import actionsPageRouter from '_pages/actions/routing';
|
||||
import conditionsPageRouter from '_pages/conditions/routing';
|
||||
import graphsPageRouter from '_pages/graphs/routing';
|
||||
import currenciesPageRouter from '_pages/currencies/routing';
|
||||
import NotFoundPage from '_pages/not-found/components/page';
|
||||
import './Page.scss';
|
||||
import MainLayout from '../main-layout';
|
||||
import jss from 'jss';
|
||||
import preset from 'jss-preset-default';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
container: {
|
||||
height: '100hv',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
jss.setup(preset());
|
||||
|
||||
const styles = {
|
||||
'@global': {
|
||||
html: {
|
||||
height: '100%',
|
||||
},
|
||||
}),
|
||||
);
|
||||
body: {
|
||||
height: '100%',
|
||||
margin: '0',
|
||||
},
|
||||
'#root': {
|
||||
height: '100%',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
jss.createStyleSheet(styles).attach();
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const store = createStore();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.container}>
|
||||
<context.Provider value={store}>
|
||||
<Container>
|
||||
<Switch>
|
||||
{mainPageRouter}
|
||||
<Route>
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Container>
|
||||
</context.Provider>
|
||||
</div>
|
||||
</Fragment>
|
||||
<context.Provider value={store}>
|
||||
<MainLayout>
|
||||
<Switch>
|
||||
{mainPageRouter}
|
||||
{usersPageRouter}
|
||||
{actionsPageRouter}
|
||||
{conditionsPageRouter}
|
||||
{graphsPageRouter}
|
||||
{currenciesPageRouter}
|
||||
<Route>
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</MainLayout>
|
||||
</context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {HashRouter} from 'react-router-dom';
|
||||
import 'antd/dist/antd.css';
|
||||
import App from './components/page';
|
||||
|
||||
ReactDOM.render(
|
||||
/*
|
||||
* Выключаем стрикт мод, пока не починят
|
||||
* https://github.com/mui-org/material-ui/issues/13394
|
||||
*/
|
||||
// <React.StrictMode>
|
||||
<HashRouter >
|
||||
<App />
|
||||
</HashRouter>,
|
||||
// </React.StrictMode>
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
87
src/core/api/CrudAPI.ts
Normal file
87
src/core/api/CrudAPI.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {isNotEmpty} from '_referers/common';
|
||||
import {http} from '../infrastructure/Http';
|
||||
|
||||
type Filter<T> = {
|
||||
field: keyof T;
|
||||
search: string;
|
||||
};
|
||||
|
||||
type Sort<T> = {
|
||||
field: keyof T;
|
||||
order: 'ASC' | 'DECS';
|
||||
};
|
||||
|
||||
type RequestEntities<T> = {
|
||||
filters?: Filter<T>[];
|
||||
sort?: Sort<T>;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
type EntityWithId<T> = T & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
type ResponseEntities<T> = {
|
||||
data: EntityWithId<T>[];
|
||||
count: number;
|
||||
total: number;
|
||||
page: number;
|
||||
pageCount: number;
|
||||
};
|
||||
|
||||
type BulkCreateRequest<T> = {
|
||||
bulk: T[];
|
||||
};
|
||||
|
||||
export class CrudAPI<T> {
|
||||
endpoint: string;
|
||||
|
||||
constructor(endpoint: string) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
request = (options?: RequestEntities<T>): Promise<ResponseEntities<T>> => {
|
||||
const {filters, sort, limit = 50, offset = 0} = options ?? {};
|
||||
const query = [];
|
||||
|
||||
filters?.forEach(({field, search}) => {
|
||||
query.push(`filter=${field}||$eq||${search}`);
|
||||
});
|
||||
|
||||
if (sort) {
|
||||
query.push(`sort=${sort.field},${sort.order}`);
|
||||
}
|
||||
|
||||
query.push(`limit=${limit}`);
|
||||
query.push(`offset=${offset}`);
|
||||
|
||||
return http.get<never, ResponseEntities<T>>([this.endpoint, query.join('&')].filter(isNotEmpty).join('?'));
|
||||
}
|
||||
|
||||
find = (id: string): Promise<T> => {
|
||||
return http.get<never, T>(`${this.endpoint}/${id}`);
|
||||
}
|
||||
|
||||
create = (entity: T): Promise<EntityWithId<T>> => {
|
||||
return http.post<never, T, EntityWithId<T>>(this.endpoint, undefined, entity);
|
||||
}
|
||||
|
||||
update = (id: string, entity: T): Promise<EntityWithId<T>> => {
|
||||
return http.patch<never, T, EntityWithId<T>>(`${this.endpoint}/${id}`, undefined, entity);
|
||||
}
|
||||
|
||||
replace = (id: string, entity: T): Promise<EntityWithId<T>> => {
|
||||
return http.put<never, T, EntityWithId<T>>(`${this.endpoint}/${id}`, undefined, entity);
|
||||
}
|
||||
|
||||
remove = (id: string): Promise<null> => {
|
||||
return http.delete<never, null>(`${this.endpoint}/${id}`);
|
||||
}
|
||||
|
||||
bulkCreate = (entities: T[]): Promise<EntityWithId<T>[]> => {
|
||||
return http.post<never, BulkCreateRequest<T>, EntityWithId<T>[]>(`${this.endpoint}/bulk`, undefined, {
|
||||
bulk: entities,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,12 @@
|
||||
export const ROUTES = {
|
||||
MAIN: '/'
|
||||
MAIN: '/',
|
||||
USERS: '/users',
|
||||
ACTIONS: '/actions',
|
||||
CONDITIONS: '/conditions',
|
||||
GRAPHS: '/graphs',
|
||||
CURRENCIES: '/currencies',
|
||||
};
|
||||
|
||||
export const ENDPOINT = {
|
||||
USERS: 'http://vigdorov.ru:3011/users',
|
||||
};
|
||||
|
||||
9
src/pages/actions/components/page/Page.tsx
Normal file
9
src/pages/actions/components/page/Page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React, {FC, memo} from 'react';
|
||||
|
||||
const Page: FC = () => {
|
||||
return (
|
||||
<div>actions</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Page);
|
||||
1
src/pages/actions/components/page/index.ts
Normal file
1
src/pages/actions/components/page/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './Page';
|
||||
8
src/pages/actions/routing.tsx
Normal file
8
src/pages/actions/routing.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {ROUTES} from '_consts/common';
|
||||
import Page from './components/page';
|
||||
|
||||
export default (
|
||||
<Route component={Page} path={ROUTES.ACTIONS} exact />
|
||||
);
|
||||
9
src/pages/conditions/components/page/Page.tsx
Normal file
9
src/pages/conditions/components/page/Page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React, {FC, memo} from 'react';
|
||||
|
||||
const Page: FC = () => {
|
||||
return (
|
||||
<div>conditions</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Page);
|
||||
1
src/pages/conditions/components/page/index.ts
Normal file
1
src/pages/conditions/components/page/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './Page';
|
||||
8
src/pages/conditions/routing.tsx
Normal file
8
src/pages/conditions/routing.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {ROUTES} from '_consts/common';
|
||||
import Page from './components/page';
|
||||
|
||||
export default (
|
||||
<Route component={Page} path={ROUTES.CONDITIONS} exact />
|
||||
);
|
||||
9
src/pages/currencies/components/page/Page.tsx
Normal file
9
src/pages/currencies/components/page/Page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React, {FC, memo} from 'react';
|
||||
|
||||
const Page: FC = () => {
|
||||
return (
|
||||
<div>currencies</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Page);
|
||||
1
src/pages/currencies/components/page/index.ts
Normal file
1
src/pages/currencies/components/page/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './Page';
|
||||
8
src/pages/currencies/routing.tsx
Normal file
8
src/pages/currencies/routing.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {ROUTES} from '_consts/common';
|
||||
import Page from './components/page';
|
||||
|
||||
export default (
|
||||
<Route component={Page} path={ROUTES.CURRENCIES} exact />
|
||||
);
|
||||
9
src/pages/graphs/components/page/Page.tsx
Normal file
9
src/pages/graphs/components/page/Page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React, {FC, memo} from 'react';
|
||||
|
||||
const Page: FC = () => {
|
||||
return (
|
||||
<div>graphs</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Page);
|
||||
1
src/pages/graphs/components/page/index.ts
Normal file
1
src/pages/graphs/components/page/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './Page';
|
||||
8
src/pages/graphs/routing.tsx
Normal file
8
src/pages/graphs/routing.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {ROUTES} from '_consts/common';
|
||||
import Page from './components/page';
|
||||
|
||||
export default (
|
||||
<Route component={Page} path={ROUTES.GRAPHS} exact />
|
||||
);
|
||||
@ -1,19 +1,11 @@
|
||||
import React, {memo} from 'react';
|
||||
import {changeNameAction, nameAtom} from '_infrastructure/atom/exampleAtom';
|
||||
import {useAction, useAtom} from '@reatom/react';
|
||||
|
||||
const MainPage: React.FC = () => {
|
||||
const name = useAtom(nameAtom);
|
||||
const handleChangeName = useAction(e => changeNameAction(e.currentTarget.value));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>main page</div>
|
||||
<div>Админка Crypto-bot</div>
|
||||
|
||||
<form>
|
||||
<label htmlFor="name">Enter your name: </label>
|
||||
<input id="name" value={name} onChange={handleChangeName} />
|
||||
</form>
|
||||
<div>Выбирите нужный раздел</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
4
src/pages/users/api/UsersAPI.ts
Normal file
4
src/pages/users/api/UsersAPI.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import {ENDPOINT} from '_consts/common';
|
||||
import {CrudAPI} from '../../../core/api/CrudAPI';
|
||||
|
||||
export const usersAPI = new CrudAPI(ENDPOINT.USERS);
|
||||
12
src/pages/users/components/page/Page.tsx
Normal file
12
src/pages/users/components/page/Page.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React, {FC, memo} from 'react';
|
||||
import {usersAPI} from '../../api/UsersAPI';
|
||||
|
||||
usersAPI.request();
|
||||
|
||||
const Page: FC = () => {
|
||||
return (
|
||||
<div>users</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Page);
|
||||
1
src/pages/users/components/page/index.ts
Normal file
1
src/pages/users/components/page/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './Page';
|
||||
8
src/pages/users/routing.tsx
Normal file
8
src/pages/users/routing.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {ROUTES} from '_consts/common';
|
||||
import Page from './components/page';
|
||||
|
||||
export default (
|
||||
<Route component={Page} path={ROUTES.USERS} exact />
|
||||
);
|
||||
Reference in New Issue
Block a user