Skip to content

Commit a0c6ce1

Browse files
committed
user list example
1 parent 6b83b90 commit a0c6ce1

12 files changed

Lines changed: 430 additions & 17 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"url": "%PROJECT-REPO%"
99
},
1010
"scripts": {
11+
"start": "react-scripts-ts start",
1112
"dev": "react-scripts-ts start",
1213
"build": "react-scripts-ts build",
1314
"eject": "react-scripts-ts eject",

src/assets/theme/overrides.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ const overrides: Overrides = {
3737
display: 'none'
3838
}
3939
}
40+
},
41+
MuiTablePagination: {
42+
input: {
43+
padding: 0,
44+
marginLeft: 7,
45+
marginRight: 32
46+
},
47+
selectRoot: {
48+
marginLeft: 0,
49+
marginRight: 0
50+
},
51+
select: {
52+
paddingRight: 20
53+
}
4054
}
4155
};
4256

src/components/Layout/Drawer/UserMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default class AppDrawerUser extends PureComponent<IProps> {
7878
<Grid item xs={true} >
7979
<Typography variant='body2' color='inherit' className={classes.text}>
8080
<small className={classes.textSmall}>Bem vindo</small>
81-
{user.firstName}
81+
{user.name}
8282
</Typography>
8383
</Grid>
8484
<Grid item>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { TableCell, TableRow } from '@material-ui/core';
2+
import ListItemComponent, { IStateListItem } from 'components/Abstract/ListItem';
3+
import Alert from 'components/Shared/Alert';
4+
import { IOption } from 'components/Shared/DropdownMenu';
5+
import Snackbar from 'components/Shared/Snackbar';
6+
import { IUser } from 'interfaces/user';
7+
import DeleteIcon from 'mdi-react/DeleteIcon';
8+
import EditIcon from 'mdi-react/EditIcon';
9+
import * as React from 'react';
10+
import rxjsOperators from 'rxjs-operators';
11+
import userService from 'services/user';
12+
13+
interface IState extends IStateListItem {
14+
deleted?: boolean;
15+
}
16+
17+
interface IProps {
18+
user: IUser;
19+
onEdit: (user: IUser) => void;
20+
onDeleteComplete: () => void;
21+
}
22+
23+
export default class ListItem extends ListItemComponent<IProps, IState> {
24+
private readonly options: IOption[];
25+
26+
constructor(props: IProps) {
27+
super(props);
28+
this.options = [{
29+
text: 'Editar',
30+
icon: EditIcon,
31+
handler: this.handleEdit
32+
}, {
33+
text: 'Excluir',
34+
icon: DeleteIcon,
35+
handler: this.handleDelete
36+
}];
37+
}
38+
39+
handleEdit = () => {
40+
const { user, onEdit } = this.props;
41+
onEdit(user);
42+
}
43+
44+
handleDelete = async () => {
45+
const { user, onDeleteComplete } = this.props;
46+
47+
const ok = await Alert.confirm(`Deseja excluir o usuário ${user.name}?`);
48+
if (!ok) return;
49+
50+
this.setState({ loading: true });
51+
52+
userService.delete(user.id).pipe(
53+
rxjsOperators.logError(),
54+
rxjsOperators.bindComponent(this)
55+
).subscribe(() => {
56+
Snackbar.show(`${user.name} foi removido`);
57+
this.setState({ loading: false, deleted: true });
58+
onDeleteComplete();
59+
}, error => {
60+
this.setState({ loading: false, error });
61+
});
62+
}
63+
64+
render(): JSX.Element {
65+
const { deleted } = this.state;
66+
const { user } = this.props;
67+
68+
if (deleted) {
69+
return null;
70+
}
71+
72+
return (
73+
<TableRow>
74+
<TableCell>{user.name}</TableCell>
75+
<TableCell>{user.email}</TableCell>
76+
<TableCell>
77+
{this.renderSideMenu(this.options)}
78+
</TableCell>
79+
</TableRow>
80+
);
81+
}
82+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Card, CardContent, Grid, IconButton, Table, TableBody, TableCell, TableHead, TableRow } from '@material-ui/core';
2+
import { IStateList, ListComponent, TableCellSortable } from 'components/Abstract/List';
3+
import Toolbar from 'components/Layout/Toolbar';
4+
import FabButton from 'components/Shared/FabButton';
5+
import TableWrapper from 'components/Shared/TableWrapper';
6+
import { IPaginationParams } from 'interfaces/pagination';
7+
import { IUser } from 'interfaces/user';
8+
import AccountPlusIcon from 'mdi-react/AccountPlusIcon';
9+
import RefreshIcon from 'mdi-react/RefreshIcon';
10+
import React, { Fragment } from 'react';
11+
import rxjsOperators from 'rxjs-operators';
12+
import userService from 'services/user';
13+
14+
import UserFormDialog from '../UserFormDialog';
15+
import ListItem from './ListItem';
16+
17+
interface IState extends IStateList<IUser> {
18+
current?: IUser;
19+
formOpened?: boolean;
20+
}
21+
22+
export default class UserListPage extends ListComponent<{}, IState> {
23+
actions = [{
24+
icon: AccountPlusIcon,
25+
onClick: () => this.handleCreate()
26+
}];
27+
28+
constructor(props: {}) {
29+
super(props, 'fullName');
30+
}
31+
32+
componentDidMount() {
33+
this.loadData();
34+
}
35+
36+
loadData = (params: Partial<IPaginationParams> = {}) => {
37+
this.setState({ loading: true, error: null });
38+
39+
userService.list(this.mergeParams(params)).pipe(
40+
rxjsOperators.logError(),
41+
rxjsOperators.bindComponent(this)
42+
).subscribe(items => {
43+
this.setPaginatedData(items);
44+
}, error => this.setError(error));
45+
}
46+
47+
handleRefresh = () => this.loadData();
48+
49+
handleCreate = () => {
50+
this.setState({ formOpened: true, current: null });
51+
}
52+
53+
handleEdit = (current: IUser) => {
54+
this.setState({ formOpened: true, current });
55+
}
56+
57+
formCallback = (user?: IUser) => {
58+
this.setState({ formOpened: false });
59+
60+
this.state.current ?
61+
this.loadData() :
62+
this.handleChangeTerm(user.email);
63+
}
64+
65+
formCancel = () => {
66+
this.setState({ formOpened: false });
67+
}
68+
69+
render() {
70+
const { items, formOpened, loading, current } = this.state;
71+
72+
return (
73+
<Fragment>
74+
<Toolbar title='Usuários' />
75+
76+
<Card>
77+
<FabButton actions={this.actions} />
78+
79+
<UserFormDialog
80+
opened={formOpened || false}
81+
user={current}
82+
onComplete={this.formCallback}
83+
onCancel={this.formCancel}
84+
/>
85+
86+
{this.renderLoader()}
87+
88+
<CardContent>
89+
<Grid container>
90+
<Grid item xs={12} sm={6} lg={4}>
91+
{this.renderSearch()}
92+
</Grid>
93+
</Grid>
94+
</CardContent>
95+
96+
<TableWrapper minWidth={500}>
97+
<Table>
98+
<TableHead>
99+
<TableRow>
100+
<TableCellSortable {...this.sortableProps} column='fullName'>
101+
Nome
102+
</TableCellSortable>
103+
<TableCellSortable {...this.sortableProps} column='email'>
104+
Email
105+
</TableCellSortable>
106+
<TableCell>
107+
<IconButton disabled={loading} onClick={this.handleRefresh}>
108+
<RefreshIcon />
109+
</IconButton>
110+
</TableCell>
111+
</TableRow>
112+
</TableHead>
113+
<TableBody>
114+
{this.renderEmptyAndErrorMessages(3)}
115+
{items.map(user =>
116+
<ListItem
117+
key={user.id}
118+
user={user}
119+
onEdit={this.handleEdit}
120+
onDeleteComplete={this.loadData}
121+
/>
122+
)}
123+
</TableBody>
124+
</Table>
125+
</TableWrapper>
126+
{this.renderTablePagination()}
127+
</Card>
128+
129+
</Fragment>
130+
);
131+
}
132+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, LinearProgress, Slide } from '@material-ui/core';
2+
import { FieldText, ValidationContext } from '@react-form-fields/material-ui';
3+
import { FormComponent, IStateForm } from 'components/Abstract/Form';
4+
import Snackbar from 'components/Shared/Snackbar';
5+
import { WithStyles } from 'decorators/withStyles';
6+
import { IUser } from 'interfaces/user';
7+
import React, { FormEvent, Fragment } from 'react';
8+
import rxjsOperators from 'rxjs-operators';
9+
import userService from 'services/user';
10+
11+
interface IState extends IStateForm<IUser> {
12+
loading: boolean;
13+
}
14+
15+
interface IProps {
16+
opened: boolean;
17+
user?: IUser;
18+
onComplete: (user: IUser) => void;
19+
onCancel: () => void;
20+
classes?: any;
21+
}
22+
23+
@WithStyles({
24+
content: {
25+
width: 400,
26+
maxWidth: 'calc(95vw - 50px)'
27+
},
28+
heading: {
29+
marginTop: 20,
30+
marginBottom: 10
31+
}
32+
})
33+
export default class UserFormDialog extends FormComponent<IProps, IState> {
34+
constructor(props: {}) {
35+
super(props);
36+
this.state = {
37+
...this.state
38+
};
39+
}
40+
41+
get isEdit(): boolean {
42+
return !!this.state.model.id;
43+
}
44+
45+
handleEnter = () => {
46+
const { user } = this.props;
47+
48+
this.setState({ model: user || {} });
49+
}
50+
51+
handleExit = () => {
52+
this.resetForm();
53+
}
54+
55+
onSubmit = (event: FormEvent) => {
56+
event.preventDefault();
57+
58+
const { model } = this.state;
59+
const { onComplete } = this.props;
60+
61+
if (!this.isFormValid()) return;
62+
63+
this.setState({ loading: true });
64+
65+
userService.save(model as IUser).pipe(
66+
rxjsOperators.logError(),
67+
rxjsOperators.bindComponent(this)
68+
).subscribe(user => {
69+
Snackbar.show(`${user.name} foi salvo${this.isEdit ? '' : ', um email foi enviado com a senha'}`);
70+
this.setState({ loading: false });
71+
72+
onComplete(user);
73+
}, err => {
74+
Snackbar.error(err.message === 'email-unavailable' ? 'Email já utlizado' : err);
75+
this.setState({ loading: false });
76+
});
77+
}
78+
79+
render() {
80+
const { model, loading } = this.state;
81+
const { opened, classes, onCancel } = this.props;
82+
83+
return (
84+
<Dialog
85+
open={opened}
86+
disableBackdropClick
87+
disableEscapeKeyDown
88+
onEnter={this.handleEnter}
89+
onExited={this.handleExit}
90+
TransitionComponent={Transition}
91+
>
92+
93+
{loading && <LinearProgress color='secondary' />}
94+
95+
<form onSubmit={this.onSubmit} noValidate>
96+
<ValidationContext ref={this.bindValidationContext}>
97+
<DialogTitle>{this.isEdit ? 'Editar' : 'Novo'} Usuário</DialogTitle>
98+
<DialogContent className={classes.content}>
99+
<Fragment>
100+
<FieldText
101+
label='Nome'
102+
disabled={loading}
103+
value={model.name}
104+
validation='required|min:3|max:50'
105+
onChange={this.updateModel((model, v) => model.name = v)}
106+
/>
107+
108+
<FieldText
109+
label='Email'
110+
type='email'
111+
disabled={loading}
112+
value={model.email}
113+
validation='required|email|max:150'
114+
onChange={this.updateModel((model, v) => model.email = v)}
115+
/>
116+
117+
</Fragment>
118+
</DialogContent>
119+
<DialogActions>
120+
<Button onClick={onCancel}>
121+
Cancelar
122+
</Button>
123+
<Button color='secondary' type='submit' disabled={loading}>
124+
Salvar
125+
</Button>
126+
</DialogActions>
127+
</ValidationContext>
128+
</form>
129+
</Dialog>
130+
);
131+
}
132+
}
133+
134+
function Transition(props: any) {
135+
return <Slide direction='up' {...props} />;
136+
}

0 commit comments

Comments
 (0)