add crud service, crud atoms, crud table, graphs page
This commit is contained in:
@ -60,8 +60,8 @@ export class CrudAPI<T> {
|
|||||||
return http.get<never, ResponseEntities<T>>([this.endpoint, query.join('&')].filter(isNotEmpty).join('?'));
|
return http.get<never, ResponseEntities<T>>([this.endpoint, query.join('&')].filter(isNotEmpty).join('?'));
|
||||||
}
|
}
|
||||||
|
|
||||||
find = (id: string): Promise<T> => {
|
find = (id: string): Promise<EntityWithId<T>> => {
|
||||||
return http.get<never, T>(`${this.endpoint}/${id}`);
|
return http.get<never, EntityWithId<T>>(`${this.endpoint}/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
create = (entity: EntityWithoutId<T>): Promise<EntityWithId<T>> => {
|
create = (entity: EntityWithoutId<T>): Promise<EntityWithId<T>> => {
|
||||||
|
|||||||
64
src/core/blocks/entity-table/EntityTable.tsx
Normal file
64
src/core/blocks/entity-table/EntityTable.tsx
Normal file
@ -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<T> = {
|
||||||
|
entityListAtom: Atom<T[]>;
|
||||||
|
service: CrudService<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEntityTable = function <T extends EntityWithId<unknown>> ({
|
||||||
|
entityListAtom,
|
||||||
|
service,
|
||||||
|
}: Props<T>): FC {
|
||||||
|
return memo(() => {
|
||||||
|
const entityList = useAtom(entityListAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
service.loadEntityList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onRow = useCallback((entity: EntityWithId<T>) => ({
|
||||||
|
onClick: () => {
|
||||||
|
service.navigate(EntityMode.Show, entity.id);
|
||||||
|
},
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const columns: ColumnsType<EntityWithId<T>> = 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 (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
onRow={onRow}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
1
src/core/blocks/entity-table/index.ts
Normal file
1
src/core/blocks/entity-table/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './EntityTable';
|
||||||
2
src/core/dts/comon.d.ts
vendored
2
src/core/dts/comon.d.ts
vendored
@ -1,3 +1,5 @@
|
|||||||
type Undefinable<T> = T | undefined;
|
type Undefinable<T> = T | undefined;
|
||||||
|
|
||||||
type Nullable<T> = T | undefined | null;
|
type Nullable<T> = T | undefined | null;
|
||||||
|
|
||||||
|
type Indexed<T> = Record<string, T>;
|
||||||
|
|||||||
28
src/core/infrastructure/atom/createEntityAtoms.ts
Normal file
28
src/core/infrastructure/atom/createEntityAtoms.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {declareAction, declareAtom} from '@reatom/core';
|
||||||
|
import {EntityWithId} from '../../api/CrudAPI';
|
||||||
|
import {store} from './store';
|
||||||
|
|
||||||
|
export const createEntityAtoms = <T>(initEntity: T) => {
|
||||||
|
const INIT_ENTITY_LIST: EntityWithId<T>[] = [];
|
||||||
|
|
||||||
|
const loadEntityList = declareAction<typeof INIT_ENTITY_LIST>();
|
||||||
|
const loadEntityForm = declareAction<T>();
|
||||||
|
|
||||||
|
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};
|
||||||
|
};
|
||||||
@ -2,14 +2,6 @@ import {declareAction, declareAtom} from '@reatom/core';
|
|||||||
import {User} from '../../../pages/users/types';
|
import {User} from '../../../pages/users/types';
|
||||||
import {store} from './store';
|
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[] = [];
|
const INIT_USERS: User[] = [];
|
||||||
export const INIT_USER: User = {
|
export const INIT_USER: User = {
|
||||||
id: '',
|
id: '',
|
||||||
|
|||||||
70
src/core/services/CrudService.ts
Normal file
70
src/core/services/CrudService.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {CrudAPI, EntityWithId, EntityWithoutId} from '../api/CrudAPI';
|
||||||
|
import {EntityMode} from '../types/EntityModes';
|
||||||
|
import {routerService} from './RouterService';
|
||||||
|
|
||||||
|
type Actions<T> = {
|
||||||
|
loadEntityList: (entities: T[]) => void,
|
||||||
|
loadEntityForm: (entity: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CrudService<T> {
|
||||||
|
api: CrudAPI<T>;
|
||||||
|
|
||||||
|
actions: Actions<T>;
|
||||||
|
|
||||||
|
route: string;
|
||||||
|
|
||||||
|
constructor(route: string, endpoint: string, actions: Actions<T>) {
|
||||||
|
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<T>) {
|
||||||
|
this.api
|
||||||
|
.update(id, entity)
|
||||||
|
.then(this.goRootAndReload);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEntity(id?: string) {
|
||||||
|
if (id) {
|
||||||
|
this.api
|
||||||
|
.remove(id)
|
||||||
|
.then(this.goRootAndReload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createEntity(entity: EntityWithoutId<T>) {
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import {decode, encode, ParsedUrlQuery} from 'querystring';
|
import {decode, encode, ParsedUrlQuery} from 'querystring';
|
||||||
import {bindedActions} from '../infrastructure/atom/routerAtom';
|
import {bindedActions} from '../infrastructure/atom/routerAtom';
|
||||||
import {isNotEmpty} from '../referers/common';
|
import {isNotEmpty} from '../referers/common';
|
||||||
|
import {objectEntries} from '../utils/objectEntries';
|
||||||
|
|
||||||
type PushQueryOptions = {
|
type PushQueryOptions = {
|
||||||
reset?: boolean;
|
reset?: boolean;
|
||||||
@ -14,7 +15,7 @@ class RouterService {
|
|||||||
bindedActions.routerAction(route);
|
bindedActions.routerAction(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
pushWithQuery(path: string, query: ParsedUrlQuery, options?: PushQueryOptions) {
|
pushWithQuery(path: string, query: Indexed<Undefinable<string | string[]>>, options?: PushQueryOptions) {
|
||||||
const currentQuery = getQuery();
|
const currentQuery = getQuery();
|
||||||
|
|
||||||
const finalQuery = encode({
|
const finalQuery = encode({
|
||||||
@ -24,7 +25,12 @@ class RouterService {
|
|||||||
...(options?.shouldRefresh ? {
|
...(options?.shouldRefresh ? {
|
||||||
__timestamp: Date.now(),
|
__timestamp: Date.now(),
|
||||||
} : {}),
|
} : {}),
|
||||||
...query,
|
...objectEntries(query).reduce<ParsedUrlQuery>((acc, [key, value]) => {
|
||||||
|
if (value) {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.push([path, finalQuery].filter(isNotEmpty).join('?'));
|
this.push([path, finalQuery].filter(isNotEmpty).join('?'));
|
||||||
|
|||||||
@ -1,8 +1,51 @@
|
|||||||
|
import {Button, Layout} from 'antd';
|
||||||
import React, {FC, memo} from 'react';
|
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<GraphModel>({
|
||||||
|
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 Page: FC = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>graphs</div>
|
<Layout>
|
||||||
|
<Layout.Header className={classes.header}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleClickNewEntity}
|
||||||
|
>
|
||||||
|
New graph
|
||||||
|
</Button>
|
||||||
|
</Layout.Header>
|
||||||
|
<Layout.Content>
|
||||||
|
<EntityTable />
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
8
src/pages/graphs/types.ts
Normal file
8
src/pages/graphs/types.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
type Graph = {
|
||||||
|
type: string;
|
||||||
|
graphName: string;
|
||||||
|
from: number;
|
||||||
|
to: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GraphModel = Record<keyof Graph, string>;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import {ENDPOINT} from '_consts/common';
|
import {ENDPOINT} from '_consts/common';
|
||||||
import {CrudAPI, EntityWithId, EntityWithoutId} from '../../../core/api/CrudAPI';
|
import {CrudAPI, EntityWithId, EntityWithoutId} from '_api/CrudAPI';
|
||||||
import {http} from '../../../core/infrastructure/Http';
|
import {http} from '_infrastructure/Http';
|
||||||
import {User} from '../types';
|
import {User} from '../types';
|
||||||
import {ChangePasswordRequest} from './types';
|
import {ChangePasswordRequest} from './types';
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import {Input, Modal} from 'antd';
|
import {Input, Modal} from 'antd';
|
||||||
import React, {FC, memo, useCallback, useState} from 'react';
|
import React, {FC, memo, useCallback, useState} from 'react';
|
||||||
import {ROUTES} from '../../../../core/consts/common';
|
import {ROUTES} from '_consts/common';
|
||||||
import {useQuery} from '../../../../core/hooks/useQuery';
|
import {useQuery} from '_hooks/useQuery';
|
||||||
import {routerService} from '../../../../core/services/RouterService';
|
import {routerService} from '_services/RouterService';
|
||||||
import {LABELS} from '../../consts';
|
import {LABELS} from '../../consts';
|
||||||
import {ModalType} from '../../enums';
|
import {ModalType} from '../../enums';
|
||||||
import {usersService} from '../../services/UsersServices';
|
import {usersService} from '../../services/UsersServices';
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Button, Layout} from 'antd';
|
import {Button, Layout} from 'antd';
|
||||||
import React, {FC, Fragment, memo} from 'react';
|
import React, {FC, Fragment, memo} from 'react';
|
||||||
import {createUseStyles} from 'react-jss';
|
import {createUseStyles} from 'react-jss';
|
||||||
import {ROUTES} from '../../../../core/consts/common';
|
import {ROUTES} from '_consts/common';
|
||||||
import {routerService} from '../../../../core/services/RouterService';
|
import {routerService} from '_services/RouterService';
|
||||||
import {EntityMode} from '../../../../core/types/EntityModes';
|
import {EntityMode} from '_types/EntityModes';
|
||||||
import ChangePasswordModal from '../change-password-modal/ChangePasswordModal';
|
import ChangePasswordModal from '../change-password-modal/ChangePasswordModal';
|
||||||
import UserSidebar from '../user-sidebar/UserSidebar';
|
import UserSidebar from '../user-sidebar/UserSidebar';
|
||||||
import UsersTable from '../users-table/UsersTable';
|
import UsersTable from '../users-table/UsersTable';
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import {useAtom} from '@reatom/react';
|
|||||||
import {Button, Drawer, Input} from 'antd';
|
import {Button, Drawer, Input} from 'antd';
|
||||||
import React, {FC, Fragment, memo, SyntheticEvent, useCallback, useEffect, useMemo} from 'react';
|
import React, {FC, Fragment, memo, SyntheticEvent, useCallback, useEffect, useMemo} from 'react';
|
||||||
import {createUseStyles} from 'react-jss';
|
import {createUseStyles} from 'react-jss';
|
||||||
import {ROUTES} from '../../../../core/consts/common';
|
import {ROUTES} from '_consts/common';
|
||||||
import {useQuery} from '../../../../core/hooks/useQuery';
|
import {useQuery} from '_hooks/useQuery';
|
||||||
import {bindedActions, userFormAtom} from '../../../../core/infrastructure/atom/usersAtom';
|
import {bindedActions, userFormAtom} from '_infrastructure/atom/usersAtom';
|
||||||
import {routerService} from '../../../../core/services/RouterService';
|
import {routerService} from '_services/RouterService';
|
||||||
import {EntityMode} from '../../../../core/types/EntityModes';
|
import {EntityMode} from '_types/EntityModes';
|
||||||
import {usersService} from '../../services/UsersServices';
|
import {usersService} from '../../services/UsersServices';
|
||||||
import {queryParsers} from '../../utils';
|
import {queryParsers} from '../../utils';
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import {Button, Table} from 'antd';
|
|||||||
import {ColumnsType} from 'antd/lib/table';
|
import {ColumnsType} from 'antd/lib/table';
|
||||||
import {head} from 'lodash';
|
import {head} from 'lodash';
|
||||||
import React, {FC, memo, useEffect, useMemo} from 'react';
|
import React, {FC, memo, useEffect, useMemo} from 'react';
|
||||||
import {ROUTES} from '../../../../core/consts/common';
|
import {ROUTES} from '_consts/common';
|
||||||
import {usersAtom} from '../../../../core/infrastructure/atom/usersAtom';
|
import {usersAtom} from '_infrastructure/atom/usersAtom';
|
||||||
import {routerService} from '../../../../core/services/RouterService';
|
import {routerService} from '_services/RouterService';
|
||||||
import {EntityMode} from '../../../../core/types/EntityModes';
|
import {EntityMode} from '_types/EntityModes';
|
||||||
import {objectKeys} from '../../../../core/utils/objectKeys';
|
import {objectKeys} from '_utils/objectKeys';
|
||||||
import {ModalType} from '../../enums';
|
import {ModalType} from '../../enums';
|
||||||
import {usersService} from '../../services/UsersServices';
|
import {usersService} from '../../services/UsersServices';
|
||||||
import {User} from '../../types';
|
import {User} from '../../types';
|
||||||
@ -37,7 +37,8 @@ const UsersTable: FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const columns: ColumnsType<User> = useMemo(() => {
|
const columns: ColumnsType<User> = useMemo(() => {
|
||||||
const user = head(users) ?? {} as User;
|
const user = head(users);
|
||||||
|
if (user) {
|
||||||
return objectKeys(user).map(field => {
|
return objectKeys(user).map(field => {
|
||||||
if (field === 'password') {
|
if (field === 'password') {
|
||||||
return {
|
return {
|
||||||
@ -54,6 +55,8 @@ const UsersTable: FC = () => {
|
|||||||
key: field,
|
key: field,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}, [users]);
|
}, [users]);
|
||||||
|
|
||||||
const dataSource = useMemo(() => {
|
const dataSource = useMemo(() => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {EntityWithoutId} from '../../../core/api/CrudAPI';
|
import {EntityWithoutId} from '_api/CrudAPI';
|
||||||
import {ROUTES} from '../../../core/consts/common';
|
import {ROUTES} from '_consts/common';
|
||||||
import {bindedActions, INIT_USER} from '../../../core/infrastructure/atom/usersAtom';
|
import {bindedActions, INIT_USER} from '_infrastructure/atom/usersAtom';
|
||||||
import {routerService} from '../../../core/services/RouterService';
|
import {routerService} from '_services/RouterService';
|
||||||
import {usersAPI} from '../api/UsersAPI';
|
import {usersAPI} from '../api/UsersAPI';
|
||||||
import {User} from '../types';
|
import {User} from '../types';
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,5 @@
|
|||||||
import {EntityMode} from '../../core/types/EntityModes';
|
|
||||||
import {ModalType} from './enums';
|
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: string;
|
id: string;
|
||||||
login: string;
|
login: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryParams = {
|
|
||||||
id?: string;
|
|
||||||
mode?: EntityMode;
|
|
||||||
modalType?: ModalType;
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {EntityMode} from '../../core/types/EntityModes';
|
import {EntityMode} from '_types/EntityModes';
|
||||||
import {stringParser} from '../../core/utils/queryParsers';
|
import {stringParser} from '_utils/queryParsers';
|
||||||
import {ModalType} from './enums';
|
import {ModalType} from './enums';
|
||||||
|
|
||||||
export const queryParsers = {
|
export const queryParsers = {
|
||||||
|
|||||||
Reference in New Issue
Block a user