change(ui) - users list
This commit is contained in:
parent
0bbd27e856
commit
7d08e32d25
13 changed files with 361 additions and 3 deletions
|
|
@ -7,6 +7,7 @@ import { fetchList as fetchMemberList } from 'Duck/member';
|
|||
import ProfileSettings from './ProfileSettings';
|
||||
import Integrations from './Integrations';
|
||||
import ManageUsers from './ManageUsers';
|
||||
import UserView from './Users/UsersView';
|
||||
import Sites from './Sites';
|
||||
import CustomFields from './CustomFields';
|
||||
import Webhooks from './Webhooks';
|
||||
|
|
@ -36,7 +37,7 @@ export default class Client extends React.PureComponent {
|
|||
<Switch>
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.PROFILE) } component={ ProfileSettings } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.INTEGRATIONS) } component={ Integrations } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_USERS) } component={ ManageUsers } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_USERS) } component={ UserView } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.SITES) } component={ Sites } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.CUSTOM_FIELDS) } component={ CustomFields } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.WEBHOOKS) } component={ Webhooks } />
|
||||
|
|
|
|||
30
frontend/app/components/Client/Users/UsersView.tsx
Normal file
30
frontend/app/components/Client/Users/UsersView.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import UserList from './components/UserList';
|
||||
import { PageTitle } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import UserSearch from './components/UserSearch';
|
||||
|
||||
function UsersView(props) {
|
||||
const { userStore } = useStore();
|
||||
const userCount = useObserver(() => userStore.list.length);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<PageTitle
|
||||
title={<div>Team <span className="color-gray-medium">{userCount}</span></div>}
|
||||
actionButton={(
|
||||
<div className="ml-2">test</div>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<UserSearch />
|
||||
</div>
|
||||
</div>
|
||||
<UserList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UsersView;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import React, { useEffect } from 'react';
|
||||
import UserListItem from '../UserListItem';
|
||||
import { sliceListPerPage } from 'App/utils';
|
||||
import { Pagination } from 'UI';
|
||||
|
||||
function UserList(props) {
|
||||
const { userStore } = useStore();
|
||||
const list = useObserver(() => userStore.list);
|
||||
const length = list.length;
|
||||
|
||||
useEffect(() => {
|
||||
userStore.fetchUsers();
|
||||
}, []);
|
||||
|
||||
return useObserver(() => (
|
||||
<div>
|
||||
<div className="mt-3 rounded bg-white">
|
||||
<div className="grid grid-cols-12 p-3 font-medium">
|
||||
<div className="col-span-5">Name</div>
|
||||
<div className="col-span-3">Role</div>
|
||||
<div className="col-span-"></div>
|
||||
</div>
|
||||
|
||||
{sliceListPerPage(list, userStore.page - 1, userStore.pageSize).map((user: any) => (
|
||||
<div key={user.id} className="">
|
||||
<UserListItem user={user} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-full flex items-center justify-center py-6">
|
||||
<Pagination
|
||||
page={userStore.page}
|
||||
totalPages={Math.ceil(length / userStore.pageSize)}
|
||||
onPageChange={(page) => userStore.updateKey('page', page)}
|
||||
limit={userStore.pageSize}
|
||||
debounceRequest={100}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default UserList;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserList'
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
user: any;
|
||||
}
|
||||
function UserListItem(props: Props) {
|
||||
const { user } = props;
|
||||
return (
|
||||
<div className="grid grid-cols-12 p-3 border-t select-none hover:bg-active-blue">
|
||||
<div className="col-span-5">{user.email}</div>
|
||||
<div className="col-span-3">
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border color-teal text-sm capitalize">
|
||||
{user.roleName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserListItem;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserListItem';
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
|
||||
let debounceUpdate: any = () => {}
|
||||
function UserSearch(props) {
|
||||
const { userStore } = useStore();
|
||||
const [query, setQuery] = useState(userStore.searchQuery);
|
||||
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce((key, value) => userStore.updateKey(key, value), 500);
|
||||
}, [])
|
||||
|
||||
const write = ({ target: { name, value } }) => {
|
||||
setQuery(value);
|
||||
debounceUpdate(name, value);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="relative" style={{ width: '300px'}}>
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-3 m-auto" size="16" />
|
||||
<input
|
||||
value={query}
|
||||
name="searchQuery"
|
||||
className="bg-white p-2 border border-gray-light rounded w-full pl-10"
|
||||
placeholder="Filter by Name, Project"
|
||||
onChange={write}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default UserSearch;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserSearch';
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
import React from 'react';
|
||||
import DashboardStore, { IDashboardSotre } from './dashboardStore';
|
||||
import MetricStore, { IMetricStore } from './metricStore';
|
||||
import UserStore from './userStore';
|
||||
import APIClient from 'App/api_client';
|
||||
import { dashboardService, metricService, sessionService } from 'App/services';
|
||||
import { dashboardService, metricService, sessionService, userService } from 'App/services';
|
||||
import SettingsStore from './settingsStore';
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: IDashboardSotre;
|
||||
metricStore: IMetricStore;
|
||||
settingsStore: SettingsStore;
|
||||
userStore: UserStore;
|
||||
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
this.metricStore = new MetricStore();
|
||||
this.settingsStore = new SettingsStore();
|
||||
this.userStore = new UserStore();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
|
|
@ -21,6 +24,7 @@ export class RootStore {
|
|||
dashboardService.initClient(client)
|
||||
metricService.initClient(client)
|
||||
sessionService.initClient(client)
|
||||
userService.initClient(client)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
70
frontend/app/mstore/types/user.ts
Normal file
70
frontend/app/mstore/types/user.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { runInAction, makeAutoObservable, observable } from 'mobx'
|
||||
import { DateTime } from 'luxon';
|
||||
export interface IUser {
|
||||
userId: string
|
||||
email: string
|
||||
createdAt: string
|
||||
isAdmin: boolean
|
||||
isSuperAdmin: boolean
|
||||
isJoined: boolean
|
||||
isExpiredInvite: boolean
|
||||
roleId: string
|
||||
roleName: string
|
||||
invitationLink: string
|
||||
|
||||
fromJson(json: any): IUser
|
||||
toJson(): any
|
||||
}
|
||||
|
||||
export default class User {
|
||||
userId: string = '';
|
||||
email: string = '';
|
||||
createdAt: string = '';
|
||||
isAdmin: boolean = false;
|
||||
isSuperAdmin: boolean = false;
|
||||
isJoined: boolean = false;
|
||||
isExpiredInvite: boolean = false;
|
||||
roleId: string = '';
|
||||
roleName: string = '';
|
||||
invitationLink: string = '';
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
userId: observable,
|
||||
email: observable,
|
||||
createdAt: observable,
|
||||
isAdmin: observable,
|
||||
isSuperAdmin: observable,
|
||||
isJoined: observable,
|
||||
isExpiredInvite: observable,
|
||||
roleId: observable,
|
||||
roleName: observable,
|
||||
invitationLink: observable,
|
||||
})
|
||||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
runInAction(() => {
|
||||
this.userId = json.id;
|
||||
this.email = json.email;
|
||||
this.createdAt = json.createdAt && DateTime.fromISO(json.createdAt || 0)
|
||||
this.isAdmin = json.admin
|
||||
this.isSuperAdmin = json.superAdmin
|
||||
this.isJoined = json.joined
|
||||
this.isExpiredInvite = json.expiredInvitation
|
||||
this.roleId = json.roleId
|
||||
this.roleName = json.roleName
|
||||
this.invitationLink = json.invitationLink
|
||||
})
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
email: this.email,
|
||||
isAdmin: this.isAdmin,
|
||||
isSuperAdmin: this.isSuperAdmin,
|
||||
roleId: this.roleId,
|
||||
}
|
||||
}
|
||||
}
|
||||
98
frontend/app/mstore/userStore.ts
Normal file
98
frontend/app/mstore/userStore.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { makeAutoObservable, observable, action } from "mobx"
|
||||
import User, { IUser } from "./types/user";
|
||||
import { userService } from "App/services";
|
||||
|
||||
export default class UserStore {
|
||||
list: IUser[] = [];
|
||||
instance: IUser|null = null;
|
||||
loading: boolean = false;
|
||||
page: number = 1;
|
||||
pageSize: number = 5;
|
||||
searchQuery: string = "";
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
instance: observable,
|
||||
updateUser: action,
|
||||
updateKey: action,
|
||||
})
|
||||
}
|
||||
|
||||
updateKey(key: string, value: any) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
updateUser(user: any) {
|
||||
Object.assign(this.instance, user);
|
||||
}
|
||||
|
||||
fetchUser(userId: string): Promise<any> {
|
||||
this.loading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.one(userId)
|
||||
.then(response => {
|
||||
this.instance = new User().fromJson(response.data);
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchUsers(): Promise<any> {
|
||||
this.loading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.all()
|
||||
.then(response => {
|
||||
console.log('list', response);
|
||||
this.list = response.map(user => new User().fromJson(user));
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
saveUser(user: IUser): Promise<any> {
|
||||
this.loading = true;
|
||||
const wasCreating = !user.userId;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.save(user)
|
||||
.then(response => {
|
||||
if (wasCreating) {
|
||||
this.list.push(new User().fromJson(response.data));
|
||||
} else {
|
||||
this.updateUser(response.data);
|
||||
}
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteUser(userId: string): Promise<any> {
|
||||
this.loading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.delete(userId)
|
||||
.then(response => {
|
||||
this.instance = null;
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
45
frontend/app/services/UserService.ts
Normal file
45
frontend/app/services/UserService.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import APIClient from 'App/api_client';
|
||||
import { IUser } from 'App/mstore/types/user'
|
||||
|
||||
export default class UserService {
|
||||
private client: APIClient;
|
||||
|
||||
constructor(client?: APIClient) {
|
||||
this.client = client ? client : new APIClient();
|
||||
}
|
||||
|
||||
initClient(client?: APIClient) {
|
||||
this.client = client || new APIClient();
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.client.get('/client/members')
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
|
||||
one(userId: string) {
|
||||
return this.client.get('/users/' + userId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
|
||||
save(user: IUser) {
|
||||
const data = user.toJson();
|
||||
if (user.userId) {
|
||||
return this.client.put('/users/' + user.userId, data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
} else {
|
||||
return this.client.post('/users', data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
}
|
||||
|
||||
delete(userId: string) {
|
||||
return this.client.delete('/users/' + userId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import DashboardService, { IDashboardService } from "./DashboardService";
|
||||
import MetricService, { IMetricService } from "./MetricService";
|
||||
import SessionSerivce from "./SessionService";
|
||||
import UserService from "./UserService";
|
||||
|
||||
export const dashboardService: IDashboardService = new DashboardService();
|
||||
export const metricService: IMetricService = new MetricService();
|
||||
export const sessionService: SessionSerivce = new SessionSerivce();
|
||||
export const sessionService: SessionSerivce = new SessionSerivce();
|
||||
export const userService: UserService = new UserService();
|
||||
Loading…
Add table
Reference in a new issue