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('?'));
|
||||
}
|
||||
|
||||
find = (id: string): Promise<T> => {
|
||||
return http.get<never, T>(`${this.endpoint}/${id}`);
|
||||
find = (id: string): Promise<EntityWithId<T>> => {
|
||||
return http.get<never, EntityWithId<T>>(`${this.endpoint}/${id}`);
|
||||
}
|
||||
|
||||
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 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 {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: User = {
|
||||
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 {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<Undefinable<string | string[]>>, options?: PushQueryOptions) {
|
||||
const currentQuery = getQuery();
|
||||
|
||||
const finalQuery = encode({
|
||||
@ -24,7 +25,12 @@ class RouterService {
|
||||
...(options?.shouldRefresh ? {
|
||||
__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('?'));
|
||||
|
||||
@ -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<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 classes = useStyles();
|
||||
|
||||
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 {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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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<User> = 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: () => (
|
||||
<Button onClick={onClick(user.id)}>Change password</Button>
|
||||
),
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: field,
|
||||
dataIndex: field,
|
||||
key: field,
|
||||
render: () => (
|
||||
<Button onClick={onClick(user.id)}>Change password</Button>
|
||||
),
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: field,
|
||||
dataIndex: field,
|
||||
key: field,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}, [users]);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user