Compare commits
13 Commits
dependabot
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed2de23a3e | ||
|
|
cd2e90d974 | ||
|
|
586bff6e58 | ||
|
|
4c0782456e | ||
|
|
d9ba634300 | ||
|
|
d06f43ff0d | ||
|
|
5087bf359b | ||
|
|
4f9b08ed11 | ||
|
|
8ca8ee061f | ||
|
|
5b6de60cfa | ||
|
|
7e0815b320 | ||
|
|
3270c4b1c0 | ||
|
|
8bd0d5bdbb |
@@ -1,7 +1,6 @@
|
||||
## Status
|
||||
|
||||
Tests: [](https://ci.net1.leoek.eu/job/swt/webclient-test)
|
||||
Build: [](https://ci.net1.leoek.eu/job/swt/webclient)
|
||||
[](https://ci.net1.leoek.eu/job/swt/verver-client)
|
||||
|
||||
## Run
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"i18next-xhr-backend": "^1.5.0",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"material-ui": "^1.0.0-beta.35",
|
||||
"material-ui": "^1.0.0-beta.43",
|
||||
"material-ui-icons": "^1.0.0-beta.17",
|
||||
"mdi-react": "^2.1.19",
|
||||
"react": "^16.2.0",
|
||||
|
||||
27
src/App.js
27
src/App.js
@@ -17,14 +17,14 @@ import configureStore from "./redux/createStore";
|
||||
import { MuiThemeProvider, createMuiTheme } from "material-ui/styles";
|
||||
|
||||
import Login from "./components/Login";
|
||||
import UserList from "./components/UserList";
|
||||
import UserList from "./components/user/UserList";
|
||||
import UserCreateScreen from "./components/UserCreateScreen";
|
||||
import RoleCreateScreen from "./components/RoleCreateScreen";
|
||||
import LandingPage from "./components/LandingPage";
|
||||
import GroupCreateScreen from "./components/GroupCreateScreen";
|
||||
import RoleList from "./components/RoleList";
|
||||
import GroupList from "./components/GroupList";
|
||||
import ContactList from "./components/ContactList";
|
||||
import { ContactListScreen, ContactDetailScreen } from "./components/contact";
|
||||
|
||||
const { store, history, persistor } = configureStore();
|
||||
const theme = createMuiTheme({
|
||||
@@ -56,7 +56,27 @@ class App extends Component {
|
||||
<div>
|
||||
<Route exact path="/" component={LandingPage} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route exact path="/contact/list" component={ContactList} />
|
||||
<Route
|
||||
exact
|
||||
path="/contact/list"
|
||||
component={ContactListScreen}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/contact/:id/:type"
|
||||
component={ContactDetailScreen}
|
||||
/>
|
||||
<Route exact path="/profile" component={UserCreateScreen} />
|
||||
<Route
|
||||
exact
|
||||
path="/profile"
|
||||
component={ContactDetailScreen}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/contact/create"
|
||||
component={ContactDetailScreen}
|
||||
/>
|
||||
<Route exact path="/user/list" component={UserList} />
|
||||
<Route
|
||||
exact
|
||||
@@ -68,7 +88,6 @@ class App extends Component {
|
||||
path="/user/create"
|
||||
component={UserCreateScreen}
|
||||
/>
|
||||
<Route exact path="/profile" component={UserCreateScreen} />
|
||||
<Route exact path="/role/list" component={RoleList} />
|
||||
<Route
|
||||
exact
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from "material-ui/styles";
|
||||
import Table, {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "material-ui/Table";
|
||||
import { connect } from "react-redux";
|
||||
import { fetchList } from "../redux/actions";
|
||||
import { getItems, getItemById } from "../redux/selectors";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import KeyboardArrowLeft from "material-ui-icons/KeyboardArrowLeft";
|
||||
import KeyboardArrowRight from "material-ui-icons/KeyboardArrowRight";
|
||||
import Screen from "./Screen";
|
||||
import { entity } from "../lib/entity";
|
||||
import { apiMethod } from "../config";
|
||||
|
||||
const actionsStyles = theme => ({
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
color: theme.palette.text.secondary,
|
||||
marginLeft: theme.spacing.unit * 2.5
|
||||
}
|
||||
});
|
||||
|
||||
class TablePaginationActions extends React.Component {
|
||||
handleBackButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page - 1);
|
||||
};
|
||||
|
||||
handleNextButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page + 1);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, count, page, rowsPerPage, theme } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<IconButton
|
||||
onClick={this.handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="Next Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const TablePaginationActionsWrapped = withStyles(actionsStyles, {
|
||||
withTheme: true
|
||||
})(TablePaginationActions);
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: "100%",
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
overflowX: "auto"
|
||||
},
|
||||
table: {
|
||||
minWidth: "20%",
|
||||
maxWidth: "90%"
|
||||
},
|
||||
tableWrapper: {
|
||||
overflowX: "auto"
|
||||
}
|
||||
});
|
||||
|
||||
class ContactList extends Component {
|
||||
componentWillMount = () => {
|
||||
const { fetchContacts } = this.props;
|
||||
fetchContacts();
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
page: 0,
|
||||
rowsPerPage: 5
|
||||
};
|
||||
}
|
||||
|
||||
handleChangePage = (event, page) => {
|
||||
this.setState({ page });
|
||||
};
|
||||
|
||||
handleChangeRowsPerPage = event => {
|
||||
this.setState({ rowsPerPage: event.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, users } = this.props;
|
||||
const { rowsPerPage, page } = this.state;
|
||||
|
||||
//console.log("Render a component", users, this.props.user(6));
|
||||
return (
|
||||
<Screen>
|
||||
<h2 align="center">Übersicht aller Mitglieder</h2>
|
||||
{users ? (
|
||||
<div>
|
||||
<Table className={classes.table} align="center">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell numeric>Mitgliedsnummer</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell style={{ width: 250 }}>Rollen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users
|
||||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
.map(user => {
|
||||
return (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell numeric>{user.id}</TableCell>
|
||||
<TableCell>
|
||||
{user.firstName} {user.lastName}
|
||||
</TableCell>
|
||||
<TableCell>{user.email}</TableCell>
|
||||
<TableCell>
|
||||
{user.roles ? (
|
||||
<div>
|
||||
{user.roles.map(role => {
|
||||
return <p key={role.id}>{role.name}</p>;
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
count={users.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onChangePage={this.handleChangePage}
|
||||
onChangeRowsPerPage={this.handleChangeRowsPerPage}
|
||||
Actions={TablePaginationActionsWrapped}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div align="center">Es existieren keine Mitglieder!</div>
|
||||
)}
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ContactList.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
users: getItems(apiMethod.list)(entity.contact)(state),
|
||||
user: id => getItemById(apiMethod.list)(entity.contact)(id)(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchContacts: fetchList(entity.contact)
|
||||
};
|
||||
|
||||
export const ConnectedContactList = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ContactList);
|
||||
|
||||
export default withStyles(styles)(ConnectedContactList);
|
||||
@@ -6,15 +6,14 @@ import { permission } from "../config/groupPermissionTypes";
|
||||
|
||||
import GroupCreateForm from "./GroupCreateForm";
|
||||
import {
|
||||
fetchList,
|
||||
fetchCreateByObject,
|
||||
fetchDeleteById,
|
||||
fetchUpdateByObject,
|
||||
fetchDetailById,
|
||||
fetchAll,
|
||||
resetGroupCreateState
|
||||
} from "../redux/actions";
|
||||
import {
|
||||
getUsers,
|
||||
getGroupFormValues,
|
||||
getTimeFetchedUsers,
|
||||
isFetchingUsers,
|
||||
@@ -25,12 +24,12 @@ import {
|
||||
getGroupById,
|
||||
isLoadedGroups,
|
||||
isLoadedGroup,
|
||||
isGroupWithErrors
|
||||
isGroupWithErrors,
|
||||
getEntityItems
|
||||
} from "../redux/selectors";
|
||||
import Screen from "./Screen";
|
||||
import PropTypes from "prop-types";
|
||||
import { entity } from "../lib/entity";
|
||||
import { getContacts } from "../redux/selectors/contactsSelectors";
|
||||
|
||||
const styles = theme => ({
|
||||
progress: {
|
||||
@@ -274,8 +273,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const groupCreatedTime = getCreateGroupTimeFetched(state);
|
||||
const createGroupError = getCreateGroupError(state);
|
||||
return {
|
||||
users: getUsers(state),
|
||||
contacts: getContacts(state),
|
||||
users: getEntityItems(entity.user)(state),
|
||||
contacts: getEntityItems(entity.contact)(state),
|
||||
group: state.group.create,
|
||||
values: getGroupFormValues(state),
|
||||
isFetching,
|
||||
@@ -291,8 +290,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchUsers: fetchList(entity.user),
|
||||
fetchContacts: fetchList(entity.contact),
|
||||
fetchUsers: fetchAll(entity.user),
|
||||
fetchContacts: fetchAll(entity.contact),
|
||||
fetchCreateGroup: fetchCreateByObject(entity.group),
|
||||
fetchGroup: fetchDetailById(entity.group),
|
||||
fetchEditGroup: fetchUpdateByObject(entity.group),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Card, CardContent, Typography } from "material-ui";
|
||||
import { withStyles } from "material-ui/styles";
|
||||
import Table, {
|
||||
TableBody,
|
||||
@@ -10,7 +11,6 @@ import Table, {
|
||||
TableRow,
|
||||
TableHead
|
||||
} from "material-ui/Table";
|
||||
import Paper from "material-ui/Paper";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import FirstPageIcon from "material-ui-icons/FirstPage";
|
||||
import KeyboardArrowLeft from "material-ui-icons/KeyboardArrowLeft";
|
||||
@@ -24,7 +24,6 @@ import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import CreateIcon from "material-ui-icons/Create";
|
||||
import DeleteIcon from "material-ui-icons/Delete";
|
||||
import Typography from "material-ui/Typography";
|
||||
import { getContacts } from "../redux/selectors/contactsSelectors";
|
||||
|
||||
const actionsStyles = theme => ({
|
||||
@@ -190,10 +189,15 @@ class GroupList extends React.Component {
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<Typography type="headline" component="h2" align="center">
|
||||
{t("group_list.header")}
|
||||
</Typography>
|
||||
<Paper className={classes.root}>
|
||||
<Card className={classes.root}>
|
||||
<CardContent>
|
||||
<Typography className={classes.title} color="textSecondary">
|
||||
{t(`group.listScreen.title`)}
|
||||
</Typography>
|
||||
<Typography variant="headline" component="h2">
|
||||
{t(`group.listScreen.headline`)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<div className={classes.tableWrapper}>
|
||||
<Table className={classes.table} align="center">
|
||||
<TableHead>
|
||||
@@ -293,7 +297,7 @@ class GroupList extends React.Component {
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</Paper>
|
||||
</Card>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -234,7 +234,6 @@ export class ReactSelect extends React.Component {
|
||||
|
||||
ReactSelect.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
multi: PropTypes.object,
|
||||
options: PropTypes.array.isRequired,
|
||||
handleChangeMulti: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -9,12 +9,11 @@ import {
|
||||
fetchDeleteById,
|
||||
fetchUpdateByObject,
|
||||
fetchDetailById,
|
||||
fetchList,
|
||||
fetchAll,
|
||||
resetRoleCreateState
|
||||
} from "../redux/actions";
|
||||
import {
|
||||
getPermissions,
|
||||
getUsers,
|
||||
getRoleFormValues,
|
||||
getTimeFetchedPermissions,
|
||||
isFetchingPermissions,
|
||||
@@ -25,7 +24,8 @@ import {
|
||||
isLoadedRole,
|
||||
getRoleById,
|
||||
isLoadedRoles,
|
||||
isRoleWithErrors
|
||||
isRoleWithErrors,
|
||||
getEntityItems
|
||||
} from "../redux/selectors";
|
||||
import Screen from "./Screen";
|
||||
import { entity } from "../lib/entity";
|
||||
@@ -241,7 +241,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
return {
|
||||
permissions: getPermissions(state),
|
||||
users: getUsers(state),
|
||||
users: getEntityItems(entity.user)(state),
|
||||
role: state.role.create,
|
||||
roleForEdit: getRoleById(id)(state),
|
||||
values: getRoleFormValues(state),
|
||||
@@ -257,10 +257,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchPermissions: fetchList(entity.permission),
|
||||
fetchUsers: fetchList(entity.user),
|
||||
fetchPermissions: fetchAll(entity.permission),
|
||||
fetchUsers: fetchAll(entity.user),
|
||||
fetchCreateRole: fetchCreateByObject(entity.role),
|
||||
fetchRoles: fetchList(entity.role),
|
||||
fetchRoles: fetchAll(entity.role),
|
||||
fetchRole: fetchDetailById(entity.role),
|
||||
fetchEditRole: fetchUpdateByObject(entity.role),
|
||||
fetchDeleteRole: fetchDeleteById(entity.role),
|
||||
|
||||
@@ -10,7 +10,7 @@ import Table, {
|
||||
TableRow,
|
||||
TableHead
|
||||
} from "material-ui/Table";
|
||||
import Paper from "material-ui/Paper";
|
||||
import { Card, CardContent, Typography } from "material-ui";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import FirstPageIcon from "material-ui-icons/FirstPage";
|
||||
import KeyboardArrowLeft from "material-ui-icons/KeyboardArrowLeft";
|
||||
@@ -30,7 +30,6 @@ import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import CreateIcon from "material-ui-icons/Create";
|
||||
import DeleteIcon from "material-ui-icons/Delete";
|
||||
import Typography from "material-ui/Typography";
|
||||
import { entity } from "../lib/entity";
|
||||
import { CircularProgress } from "material-ui";
|
||||
|
||||
@@ -205,10 +204,15 @@ class RoleList extends React.Component {
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<Typography type="headline" component="h2" align="center">
|
||||
{t("role_list.header")}
|
||||
</Typography>
|
||||
<Paper className={classes.root}>
|
||||
<Card className={classes.root}>
|
||||
<CardContent>
|
||||
<Typography className={classes.title} color="textSecondary">
|
||||
{t(`role.listScreen.title`)}
|
||||
</Typography>
|
||||
<Typography variant="headline" component="h2">
|
||||
{t(`role.listScreen.headline`)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<div className={classes.tableWrapper}>
|
||||
<Table className={classes.table} align="center">
|
||||
<TableHead>
|
||||
@@ -288,7 +292,7 @@ class RoleList extends React.Component {
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</Paper>
|
||||
</Card>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,9 +93,18 @@ const styles = theme => {
|
||||
...theme.mixins.toolbar
|
||||
},
|
||||
content: {
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
maxWidth: "100%",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing.unit * 3
|
||||
padding: theme.spacing.unit * 5,
|
||||
paddingTop: 64 + theme.spacing.unit * 5
|
||||
},
|
||||
contentDrawerOpen: {
|
||||
marginLeft: drawerWidth - 16
|
||||
},
|
||||
contentDrawerClosed: {
|
||||
marginLeft: closedDrawerWidth - 16
|
||||
},
|
||||
noTextDecoration: {
|
||||
textDecoration: "none"
|
||||
@@ -231,8 +240,14 @@ export class Screen extends React.Component {
|
||||
open={open}
|
||||
/>
|
||||
</Drawer>
|
||||
<main className={classes.content}>
|
||||
<div className={classes.toolbar} />
|
||||
|
||||
<div className={classes.toolbar} />
|
||||
<main
|
||||
className={classNames(
|
||||
classes.content,
|
||||
open ? classes.contentDrawerOpen : classes.contentDrawerClosed
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +77,8 @@ export class UserCreateForm extends React.Component {
|
||||
checkedEnabled,
|
||||
canSubmit,
|
||||
id,
|
||||
t
|
||||
t,
|
||||
canDelete
|
||||
} = this.props;
|
||||
var validID = !!id || id === 0;
|
||||
return (
|
||||
@@ -151,7 +152,8 @@ export class UserCreateForm extends React.Component {
|
||||
<Checkbox
|
||||
checked={checkedAdmin}
|
||||
name={"checkedAdmin"}
|
||||
onChange={handleChangeCheckbox}
|
||||
disabled={!canDelete}
|
||||
onChange={canDelete ? handleChangeCheckbox : undefined}
|
||||
label={t("user.checkBoxAdmin")}
|
||||
/>
|
||||
</div>
|
||||
@@ -171,7 +173,7 @@ export class UserCreateForm extends React.Component {
|
||||
<Button color="primary">{t("user.cancel")}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{!!validID ? (
|
||||
{!!validID && canDelete ? (
|
||||
<div className={classes.buttonRow}>
|
||||
<Button onClick={handleDelete} color="primary">
|
||||
{t("user.delete")}
|
||||
|
||||
@@ -2,22 +2,19 @@ import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { CircularProgress, withStyles, Snackbar } from "material-ui";
|
||||
|
||||
import { get } from "lodash";
|
||||
import UserCreateForm from "./UserCreateForm";
|
||||
import {
|
||||
fetchList,
|
||||
fetchCreateByObject,
|
||||
fetchDeleteById,
|
||||
fetchUpdateByObject,
|
||||
fetchDetailById,
|
||||
fetchAll,
|
||||
resetUserCreateState
|
||||
} from "../redux/actions";
|
||||
import {
|
||||
getUsers,
|
||||
getTimeFetchedUsers,
|
||||
isFetchingUsers,
|
||||
getGroups,
|
||||
getRoles,
|
||||
getUserFormValues,
|
||||
isCreatingUser,
|
||||
isEditedUser,
|
||||
@@ -26,12 +23,13 @@ import {
|
||||
getUserById,
|
||||
isLoadedUsers,
|
||||
isLoadedUser,
|
||||
isUserWithErrors
|
||||
isUserWithErrors,
|
||||
getEntityItems,
|
||||
getUserIdFromToken
|
||||
} from "../redux/selectors";
|
||||
import Screen from "./Screen";
|
||||
import PropTypes from "prop-types";
|
||||
import { entity } from "../lib/entity";
|
||||
import { getContacts } from "../redux/selectors/contactsSelectors";
|
||||
|
||||
const styles = theme => ({
|
||||
progress: {
|
||||
@@ -89,10 +87,10 @@ export class UserCreateScreen extends Component {
|
||||
fetchUser,
|
||||
id
|
||||
} = this.props;
|
||||
fetchUsers({});
|
||||
fetchContacts({});
|
||||
fetchRoles({});
|
||||
fetchGroups({});
|
||||
fetchUsers();
|
||||
fetchContacts();
|
||||
fetchRoles();
|
||||
fetchGroups();
|
||||
if (!!id || id === 0) {
|
||||
fetchUser(id);
|
||||
}
|
||||
@@ -192,7 +190,8 @@ export class UserCreateScreen extends Component {
|
||||
isUserWithErrors,
|
||||
values,
|
||||
id,
|
||||
t
|
||||
t,
|
||||
canDelete
|
||||
} = this.props;
|
||||
const {
|
||||
username,
|
||||
@@ -283,6 +282,7 @@ export class UserCreateScreen extends Component {
|
||||
onSubmit={this.handleSubmit}
|
||||
canSubmit={canSubmit}
|
||||
id={id}
|
||||
canDelete={canDelete}
|
||||
/>
|
||||
{alert}
|
||||
<Snackbar
|
||||
@@ -300,7 +300,10 @@ export class UserCreateScreen extends Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const id = parseInt(ownProps.match.params.id, 10);
|
||||
const isProfile = get(ownProps, "match.path") === "/profile";
|
||||
const id = isProfile
|
||||
? getUserIdFromToken(state)
|
||||
: parseInt(ownProps.match.params.id, 10);
|
||||
const isFetching = isFetchingUsers(state);
|
||||
//todo replace with timeFetchedUsers
|
||||
const timeFetchedUsers = getTimeFetchedUsers(state);
|
||||
@@ -309,19 +312,21 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const isEdited = isEditedUser(state);
|
||||
const groupCreatedTime = getTimeFetchedUserList(state);
|
||||
const createUserError = getCreateUserError(state);
|
||||
const user = getUserById(id)(state);
|
||||
const values = getUserFormValues(state);
|
||||
return {
|
||||
users: getUsers(state),
|
||||
roles: getRoles(state),
|
||||
groups: getGroups(state),
|
||||
contacts: getContacts(state),
|
||||
|
||||
values: getUserFormValues(state),
|
||||
users: getEntityItems(entity.user)(state),
|
||||
roles: getEntityItems(entity.role)(state),
|
||||
groups: getEntityItems(entity.group)(state),
|
||||
contacts: getEntityItems(entity.contact)(state),
|
||||
values,
|
||||
canDelete: !(values || {}).admin && !(user || {}).admin,
|
||||
isFetching,
|
||||
isLoaded: !isFetching && !!timeFetchedUsers,
|
||||
isSent: !isCreating && !!groupCreatedTime && !createUserError,
|
||||
isEdited,
|
||||
id,
|
||||
userForEdit: getUserById(id)(state),
|
||||
userForEdit: user,
|
||||
isLoadedUsers: isLoadedUsers(state),
|
||||
isLoadedUser: isLoadedUser(state),
|
||||
isUserWithErrors: isUserWithErrors(state)
|
||||
@@ -329,10 +334,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchUsers: fetchList(entity.user),
|
||||
fetchRoles: fetchList(entity.role),
|
||||
fetchGroups: fetchList(entity.group),
|
||||
fetchContacts: fetchList(entity.contact),
|
||||
fetchUsers: fetchAll(entity.user),
|
||||
fetchRoles: fetchAll(entity.role),
|
||||
fetchGroups: fetchAll(entity.group),
|
||||
fetchContacts: fetchAll(entity.contact),
|
||||
|
||||
fetchCreateUser: fetchCreateByObject(entity.user),
|
||||
fetchUser: fetchDetailById(entity.user),
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from "material-ui/styles";
|
||||
import Table, {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "material-ui/Table";
|
||||
import { connect } from "react-redux";
|
||||
import { fetchList } from "../redux/actions";
|
||||
import { getUsers as getUsersFromState, getUserById } from "../redux/selectors";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import KeyboardArrowLeft from "material-ui-icons/KeyboardArrowLeft";
|
||||
import KeyboardArrowRight from "material-ui-icons/KeyboardArrowRight";
|
||||
import Screen from "./Screen";
|
||||
import { entity } from "../lib/entity";
|
||||
import { Link } from "react-router-dom";
|
||||
import CreateIcon from "material-ui-icons/Create";
|
||||
import { translate } from "react-i18next";
|
||||
import Typography from "material-ui/Typography";
|
||||
|
||||
const actionsStyles = theme => ({
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
color: theme.palette.text.secondary,
|
||||
marginLeft: theme.spacing.unit * 2.5
|
||||
}
|
||||
});
|
||||
|
||||
class TablePaginationActions extends React.Component {
|
||||
handleBackButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page - 1);
|
||||
};
|
||||
|
||||
handleNextButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page + 1);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, count, page, rowsPerPage, theme } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<IconButton
|
||||
onClick={this.handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="Next Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const TablePaginationActionsWrapped = withStyles(actionsStyles, {
|
||||
withTheme: true
|
||||
})(TablePaginationActions);
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: "100%",
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
overflowX: "auto"
|
||||
},
|
||||
table: {
|
||||
width: "30%"
|
||||
},
|
||||
tableWrapper: {
|
||||
overflowX: "auto"
|
||||
}
|
||||
});
|
||||
|
||||
class UserList extends Component {
|
||||
componentWillMount = () => {
|
||||
const { fetchUsers } = this.props;
|
||||
fetchUsers();
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
page: 0,
|
||||
rowsPerPage: 5
|
||||
};
|
||||
}
|
||||
|
||||
handleChangePage = (event, page) => {
|
||||
this.setState({ page });
|
||||
};
|
||||
|
||||
handleChangeRowsPerPage = event => {
|
||||
this.setState({ rowsPerPage: event.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, users, t } = this.props;
|
||||
const { rowsPerPage, page } = this.state;
|
||||
|
||||
//console.log("Render a component", users, this.props.user(6));
|
||||
return (
|
||||
<Screen>
|
||||
<Typography variant="title" align="center" gutterBottom>
|
||||
{t("user_list.header")}
|
||||
</Typography>
|
||||
{users ? (
|
||||
<div>
|
||||
<Table className={classes.table} align="center">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{t("user_list.name")}</TableCell>
|
||||
<TableCell>{t("user_list.edit")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users
|
||||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
.map(user => {
|
||||
return (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell>{user.username}</TableCell>
|
||||
<TableCell>
|
||||
<Link to={`/user/edit/${user.id}`}>
|
||||
<CreateIcon />
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
count={users.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onChangePage={this.handleChangePage}
|
||||
onChangeRowsPerPage={this.handleChangeRowsPerPage}
|
||||
Actions={TablePaginationActionsWrapped}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
) : null}
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserList.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
users: getUsersFromState(state),
|
||||
user: id => getUserById(id)(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchUsers: fetchList(entity.user)
|
||||
};
|
||||
|
||||
export const ConnectedUserList = connect(mapStateToProps, mapDispatchToProps)(
|
||||
UserList
|
||||
);
|
||||
|
||||
export default withStyles(styles)(translate()(ConnectedUserList));
|
||||
226
src/components/contact/ContactDetailScreen.js
Normal file
226
src/components/contact/ContactDetailScreen.js
Normal file
@@ -0,0 +1,226 @@
|
||||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
withStyles,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
CircularProgress
|
||||
} from "material-ui";
|
||||
import PropTypes from "prop-types";
|
||||
import { initialize } from "redux-form";
|
||||
import { get } from "lodash";
|
||||
import { goBack } from "react-router-redux";
|
||||
|
||||
import {
|
||||
getContactById,
|
||||
getEntityItems,
|
||||
getContactFormValues,
|
||||
getIsFetching,
|
||||
getContactIdFromToken
|
||||
} from "../../redux/selectors";
|
||||
import { entity } from "../../lib/entity";
|
||||
import { detailScreenType, apiMethod } from "../../config";
|
||||
|
||||
import Screen from "../Screen";
|
||||
import ContactForm from "./ContactForm";
|
||||
import {
|
||||
fetchUpdateByObject,
|
||||
fetchCreateByObject,
|
||||
fetchDetailById,
|
||||
fetchAll
|
||||
} from "../../redux/actions";
|
||||
|
||||
const styles = theme => ({
|
||||
progress: {
|
||||
margin: theme.spacing.unit * 2
|
||||
},
|
||||
progressContainer: {
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center"
|
||||
},
|
||||
card: {
|
||||
height: "100%",
|
||||
padding: theme.spacing.unit * 2
|
||||
},
|
||||
bullet: {
|
||||
display: "inline-block",
|
||||
margin: "0 2px",
|
||||
transform: "scale(0.8)"
|
||||
},
|
||||
title: {
|
||||
marginBottom: 16,
|
||||
fontSize: 14
|
||||
},
|
||||
pos: {
|
||||
marginBottom: 12
|
||||
}
|
||||
});
|
||||
|
||||
export class DetailScreen extends Component {
|
||||
state = {
|
||||
multiGroups: []
|
||||
};
|
||||
|
||||
componentWillMount = () => {
|
||||
const { fetchAllGroups } = this.props;
|
||||
this.fetchContact();
|
||||
fetchAllGroups();
|
||||
};
|
||||
|
||||
fetchContact = () => {
|
||||
const { fetchContactDetailById, type, id } = this.props;
|
||||
if (type === detailScreenType.edit || type === detailScreenType.view) {
|
||||
if (!!id || id === 0) {
|
||||
fetchContactDetailById(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//TODO: fix the React Select and make it a proper redux forms field
|
||||
handleChangeMultiGroups = multiGroups => {
|
||||
this.setState({
|
||||
multiGroups: !!multiGroups
|
||||
? multiGroups.split(",").map(v => parseInt(v, 10))
|
||||
: []
|
||||
});
|
||||
};
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
const { contact } = this.props;
|
||||
if (contact !== nextProps.contact && nextProps.contact) {
|
||||
this.setState({
|
||||
multiGroups: nextProps.contact.groups
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = values => {
|
||||
const { multiGroups } = this.state;
|
||||
const { type, fetchUpdateByObject, fetchCreateByObject } = this.props;
|
||||
|
||||
if (type === detailScreenType.edit) {
|
||||
fetchUpdateByObject({
|
||||
...values,
|
||||
groups: multiGroups
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (type === detailScreenType.create) {
|
||||
fetchCreateByObject({
|
||||
...values,
|
||||
groups: multiGroups
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
handleCancel = () => {
|
||||
const { goBack } = this.props;
|
||||
goBack();
|
||||
return true;
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const { multiGroups } = this.state;
|
||||
console.log(multiGroups);
|
||||
const { classes, t, contact, name, type, groups, isLoading } = this.props;
|
||||
const optionsGroups = (groups || []).map(group => ({
|
||||
value: group.id,
|
||||
label: group.name
|
||||
}));
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Screen>
|
||||
<div className={classes.progressContainer}>
|
||||
<CircularProgress className={classes.progress} />
|
||||
</div>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Screen>
|
||||
<div>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography className={classes.title} color="textSecondary">
|
||||
{t(`contact.detailScreen.title.${type}`)}
|
||||
</Typography>
|
||||
<Typography variant="headline" component="h2">
|
||||
{t(`contact.detailScreen.headline.${type}`, { name })}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<ContactForm
|
||||
initialValues={contact}
|
||||
initialize={initialize}
|
||||
enableReinitialize={true}
|
||||
onSubmit={this.handleSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
optionsGroups={optionsGroups}
|
||||
multiGroups={multiGroups}
|
||||
handleChangeMultiGroups={this.handleChangeMultiGroups}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</Screen>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const isProfile = get(ownProps, "match.path") === "/profile";
|
||||
const id = isProfile
|
||||
? getContactIdFromToken(state)
|
||||
: get(ownProps, "match.params.id");
|
||||
const type = get(ownProps, "match.params.type") || "create";
|
||||
const contact = getContactById(id)(state);
|
||||
const groups = getEntityItems(entity.group)(state);
|
||||
|
||||
const isFetchingContact = getIsFetching(apiMethod.detail)(entity.contact)(
|
||||
state
|
||||
);
|
||||
const isFetchingGroups = getIsFetching(apiMethod.all)(entity.group)(state);
|
||||
|
||||
const values = getContactFormValues(state);
|
||||
|
||||
return {
|
||||
isLoading: isFetchingContact || isFetchingGroups,
|
||||
id,
|
||||
type,
|
||||
contact,
|
||||
groups,
|
||||
values,
|
||||
name:
|
||||
contact &&
|
||||
contact.firstName &&
|
||||
contact.lastName &&
|
||||
`${contact.firstName} ${contact.lastName}`
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
initialize,
|
||||
goBack,
|
||||
fetchCreateByObject: fetchCreateByObject(entity.contact),
|
||||
fetchUpdateByObject: fetchUpdateByObject(entity.contact),
|
||||
fetchContactDetailById: fetchDetailById(entity.contact),
|
||||
fetchAllGroups: fetchAll(entity.group)
|
||||
};
|
||||
|
||||
DetailScreen.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
classes: PropTypes.object.isRequired,
|
||||
initialize: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export const ConnectedDetailScreen = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DetailScreen);
|
||||
|
||||
export default withStyles(styles)(translate()(ConnectedDetailScreen));
|
||||
142
src/components/contact/ContactForm.js
Normal file
142
src/components/contact/ContactForm.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from "material-ui/styles";
|
||||
import { Button, CardContent, CardActions, Grid } from "material-ui";
|
||||
import { reduxForm, Form } from "redux-form";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
import { FormTextField, formValidations } from "../layout/FormFields";
|
||||
import ReactSelect from "../ReactSelect";
|
||||
|
||||
const styles = theme => ({
|
||||
form: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
flexGrow: 1
|
||||
},
|
||||
actions: {
|
||||
width: "100%"
|
||||
},
|
||||
reactSelect: {
|
||||
width: "100%"
|
||||
},
|
||||
formItem: {
|
||||
margin: 8,
|
||||
marginTop: 22
|
||||
}
|
||||
});
|
||||
|
||||
export class ContactForm extends Component {
|
||||
render() {
|
||||
const {
|
||||
handleSubmit,
|
||||
onCancel,
|
||||
invalid,
|
||||
submitting,
|
||||
asyncValidating,
|
||||
classes,
|
||||
t,
|
||||
optionsGroups = [],
|
||||
handleChangeMultiGroups,
|
||||
multiGroups
|
||||
} = this.props;
|
||||
const canSubmit = !(submitting || invalid || asyncValidating === true);
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} className={classes.form}>
|
||||
<CardContent>
|
||||
<Grid container spacing={24}>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<FormTextField
|
||||
label={t("contact.label.email")}
|
||||
name="email"
|
||||
validate={[formValidations.required, formValidations.email]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<FormTextField
|
||||
label={t("contact.label.firstname")}
|
||||
name="firstName"
|
||||
validate={[formValidations.required]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<FormTextField
|
||||
label={t("contact.label.lastname")}
|
||||
name="lastName"
|
||||
validate={[formValidations.required]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<div className={classes.formItem}>
|
||||
{/*TODO: fix the reactselect component to work with redux-forms.*/}
|
||||
<ReactSelect
|
||||
className={classes.reactSelect}
|
||||
name={"selectGroups"}
|
||||
options={optionsGroups}
|
||||
handleChangeMulti={handleChangeMultiGroups}
|
||||
multi={multiGroups}
|
||||
canAddMultipleValues={true}
|
||||
placeholder={t("contact.placeholder.groups")}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<FormTextField
|
||||
label={t("contact.label.phone")}
|
||||
name="phone"
|
||||
validate={[formValidations.phone]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<FormTextField
|
||||
label={t("contact.label.address")}
|
||||
name="address"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<FormTextField
|
||||
label={t("contact.label.bankDetails")}
|
||||
name="bankDetails"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions className={classes.actions}>
|
||||
<Grid
|
||||
container
|
||||
spacing={24}
|
||||
alignItems={"flex-end"}
|
||||
justify={"flex-end"}
|
||||
>
|
||||
<Grid item>
|
||||
<Button color="primary" size="large" onClick={onCancel}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={!canSubmit}
|
||||
size="large"
|
||||
>
|
||||
{t("contact.detailScreen.saveButtonLabel")}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ContactForm.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool,
|
||||
groups: PropTypes.array
|
||||
};
|
||||
|
||||
export default reduxForm({ form: "contact" })(
|
||||
withStyles(styles)(translate()(ContactForm))
|
||||
);
|
||||
294
src/components/contact/ContactListScreen.js
Normal file
294
src/components/contact/ContactListScreen.js
Normal file
@@ -0,0 +1,294 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from "material-ui/styles";
|
||||
import Table, {
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
TableRow
|
||||
} from "material-ui/Table";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import FirstPageIcon from "material-ui-icons/FirstPage";
|
||||
import KeyboardArrowLeft from "material-ui-icons/KeyboardArrowLeft";
|
||||
import KeyboardArrowRight from "material-ui-icons/KeyboardArrowRight";
|
||||
import LastPageIcon from "material-ui-icons/LastPage";
|
||||
import CreateIcon from "material-ui-icons/Create";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { CircularProgress, Card, CardContent, Typography } from "material-ui";
|
||||
|
||||
import { apiMethod } from "../../config";
|
||||
import { entity } from "../../lib/entity";
|
||||
import {
|
||||
getMeta,
|
||||
getItems,
|
||||
getIsFetching,
|
||||
getTimeFetched
|
||||
} from "../../redux/selectors";
|
||||
import { fetchList } from "../../redux/actions";
|
||||
|
||||
import Screen from "../Screen";
|
||||
|
||||
const actionsStyles = theme => ({
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
color: theme.palette.text.secondary,
|
||||
marginLeft: theme.spacing.unit * 2.5
|
||||
}
|
||||
});
|
||||
|
||||
class TablePaginationActions extends React.Component {
|
||||
handleFirstPageButtonClick = event => {
|
||||
this.props.onChangePage(event, 0);
|
||||
};
|
||||
|
||||
handleBackButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page - 1);
|
||||
};
|
||||
|
||||
handleNextButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page + 1);
|
||||
};
|
||||
|
||||
handleLastPageButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.totalPages - 1);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, page, theme, meta } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<IconButton
|
||||
onClick={this.handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="First Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleNextButtonClick}
|
||||
disabled={!meta.hasNext}
|
||||
aria-label="Next Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleLastPageButtonClick}
|
||||
disabled={page === meta.totalPages - 1}
|
||||
aria-label="Last Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const TablePaginationActionsWrapped = withStyles(actionsStyles, {
|
||||
withTheme: true
|
||||
})(TablePaginationActions);
|
||||
|
||||
const ConnectedTablePaginationActionsWrapped = connect(
|
||||
state => ({ meta: getMeta(apiMethod.list)(entity.contact)(state) }),
|
||||
null
|
||||
)(TablePaginationActionsWrapped);
|
||||
|
||||
const styles = theme => ({
|
||||
progress: {
|
||||
margin: theme.spacing.unit * 2
|
||||
},
|
||||
progressContainer: {
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center"
|
||||
},
|
||||
card: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
padding: theme.spacing.unit * 2
|
||||
},
|
||||
root: {
|
||||
width: "100%",
|
||||
marginTop: theme.spacing.unit * 3
|
||||
},
|
||||
table: {
|
||||
minWidth: 500
|
||||
},
|
||||
wrapper: {
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
width: "100%"
|
||||
},
|
||||
tableWrapper: {
|
||||
overflowX: "auto"
|
||||
}
|
||||
});
|
||||
|
||||
class ContactList extends React.Component {
|
||||
componentWillMount = () => {
|
||||
const { fetchContactList, meta } = this.props;
|
||||
const { page = 0, size = 10 } = meta || {};
|
||||
fetchContactList({ page, size });
|
||||
};
|
||||
|
||||
handleChangePage = (event, page) => {
|
||||
const { fetchContactList, meta = {} } = this.props;
|
||||
fetchContactList({ page, size: meta.size });
|
||||
};
|
||||
|
||||
handleChangeSize = event => {
|
||||
const { fetchContactList, meta = {} } = this.props;
|
||||
fetchContactList({ page: meta.page, size: event.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, isLoading, contacts, meta = {}, t } = this.props;
|
||||
const emptyRows = 0;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Screen>
|
||||
<div className={classes.progressContainer}>
|
||||
<CircularProgress className={classes.progress} />
|
||||
</div>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<div className={classes.wrapper}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography className={classes.title} color="textSecondary">
|
||||
{t(`contact.listScreen.title`)}
|
||||
</Typography>
|
||||
<Typography variant="headline" component="h2">
|
||||
{t(`contact.listScreen.headline`)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<div className={classes.tableWrapper}>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{t("contact.label.firstname")}</TableCell>
|
||||
<TableCell>{t("contact.label.lastname")}</TableCell>
|
||||
<TableCell>{t("contact.label.email")}</TableCell>
|
||||
<TableCell>{t("contact.label.phone")}</TableCell>
|
||||
<TableCell>{t("contact.label.address")}</TableCell>
|
||||
<TableCell>{t("contact.label.bankDetails")}</TableCell>
|
||||
<TableCell>{t("contact.label.edit")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{contacts.map(contact => {
|
||||
return (
|
||||
<TableRow key={contact.id}>
|
||||
<TableCell>{contact.firstName}</TableCell>
|
||||
<TableCell>{contact.lastName}</TableCell>
|
||||
<TableCell>{contact.email}</TableCell>
|
||||
<TableCell>{contact.phone}</TableCell>
|
||||
<TableCell>{contact.address}</TableCell>
|
||||
<TableCell>{contact.bankDetails}</TableCell>
|
||||
<TableCell>
|
||||
<Link to={`/contact/${contact.id}/edit`}>
|
||||
<CreateIcon />
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: 48 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
count={meta.totalElements}
|
||||
rowsPerPage={meta.size}
|
||||
page={meta.page}
|
||||
onChangePage={this.handleChangePage}
|
||||
onChangeRowsPerPage={this.handleChangeSize}
|
||||
Actions={ConnectedTablePaginationActionsWrapped}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const contacts = getItems(apiMethod.list)(entity.contact)(state);
|
||||
const meta = getMeta(apiMethod.list)(entity.contact)(state);
|
||||
const isFetchingContacts = getIsFetching(apiMethod.list)(entity.contact)(
|
||||
state
|
||||
);
|
||||
const timeFetchedContacts = getTimeFetched(apiMethod.list)(entity.contact)(
|
||||
state
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading: !timeFetchedContacts || isFetchingContacts,
|
||||
isLoaded: !!timeFetchedContacts,
|
||||
contacts,
|
||||
meta
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchContactList: fetchList(entity.contact)
|
||||
};
|
||||
|
||||
ContactList.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const ConnectedContactList = connect(mapStateToProps, mapDispatchToProps)(
|
||||
ContactList
|
||||
);
|
||||
|
||||
export default translate()(withStyles(styles)(ConnectedContactList));
|
||||
4
src/components/contact/index.js
Normal file
4
src/components/contact/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import ContactListScreen from "./ContactListScreen";
|
||||
import ContactDetailScreen from "./ContactDetailScreen";
|
||||
|
||||
export { ContactDetailScreen, ContactListScreen };
|
||||
56
src/components/layout/FormFields.js
Normal file
56
src/components/layout/FormFields.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { TextField, withStyles } from "material-ui";
|
||||
import { Field } from "redux-form";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
export const formValidations = {
|
||||
email: value =>
|
||||
value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
|
||||
? i18n.t("validation.invalid_email_address")
|
||||
: undefined,
|
||||
required: value => (value ? undefined : i18n.t("validation.required")),
|
||||
phone: value =>
|
||||
value && !/^[0-9\- +]{4,15}$/i.test(value)
|
||||
? i18n.t("validation.invalid_phone_number")
|
||||
: undefined
|
||||
};
|
||||
|
||||
const styles = theme => ({
|
||||
formItem: {
|
||||
margin: theme.spacing.unit,
|
||||
width: "100%"
|
||||
}
|
||||
});
|
||||
|
||||
const renderTextField = ({
|
||||
meta: { touched, error },
|
||||
input: { value, ...inputRest },
|
||||
...rest
|
||||
}) => (
|
||||
<TextField
|
||||
{...rest}
|
||||
{...inputRest}
|
||||
value={value ? value : ""}
|
||||
error={!!(touched && error)}
|
||||
helperText={touched && error}
|
||||
/>
|
||||
);
|
||||
|
||||
const FormTextFieldBase = ({ label, name, classNames, classes, ...rest }) => (
|
||||
<Field
|
||||
label={label ? label : name}
|
||||
name={name}
|
||||
component={renderTextField}
|
||||
className={classNames ? classNames : classes.formItem}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
FormTextFieldBase.propTypes = {
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
classes: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export const FormTextField = withStyles(styles)(FormTextFieldBase);
|
||||
282
src/components/user/UserList.js
Normal file
282
src/components/user/UserList.js
Normal file
@@ -0,0 +1,282 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from "material-ui/styles";
|
||||
import Table, {
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
TableRow
|
||||
} from "material-ui/Table";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import FirstPageIcon from "material-ui-icons/FirstPage";
|
||||
import KeyboardArrowLeft from "material-ui-icons/KeyboardArrowLeft";
|
||||
import KeyboardArrowRight from "material-ui-icons/KeyboardArrowRight";
|
||||
import LastPageIcon from "material-ui-icons/LastPage";
|
||||
import CreateIcon from "material-ui-icons/Create";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { CircularProgress, Card, CardContent, Typography } from "material-ui";
|
||||
|
||||
import { apiMethod } from "../../config";
|
||||
import { entity } from "../../lib/entity";
|
||||
import {
|
||||
getMeta,
|
||||
getItems,
|
||||
getIsFetching,
|
||||
getTimeFetched
|
||||
} from "../../redux/selectors";
|
||||
import { fetchList } from "../../redux/actions";
|
||||
|
||||
import Screen from "../Screen";
|
||||
|
||||
const actionsStyles = theme => ({
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
color: theme.palette.text.secondary,
|
||||
marginLeft: theme.spacing.unit * 2.5
|
||||
}
|
||||
});
|
||||
|
||||
class TablePaginationActions extends React.Component {
|
||||
handleFirstPageButtonClick = event => {
|
||||
this.props.onChangePage(event, 0);
|
||||
};
|
||||
|
||||
handleBackButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page - 1);
|
||||
};
|
||||
|
||||
handleNextButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.page + 1);
|
||||
};
|
||||
|
||||
handleLastPageButtonClick = event => {
|
||||
this.props.onChangePage(event, this.props.totalPages - 1);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, page, theme, meta } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<IconButton
|
||||
onClick={this.handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="First Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleNextButtonClick}
|
||||
disabled={!meta.hasNext}
|
||||
aria-label="Next Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.handleLastPageButtonClick}
|
||||
disabled={page === meta.totalPages - 1}
|
||||
aria-label="Last Page"
|
||||
>
|
||||
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const TablePaginationActionsWrapped = withStyles(actionsStyles, {
|
||||
withTheme: true
|
||||
})(TablePaginationActions);
|
||||
|
||||
const ConnectedTablePaginationActionsWrapped = connect(
|
||||
state => ({ meta: getMeta(apiMethod.list)(entity.user)(state) }),
|
||||
null
|
||||
)(TablePaginationActionsWrapped);
|
||||
|
||||
const styles = theme => ({
|
||||
progress: {
|
||||
margin: theme.spacing.unit * 2
|
||||
},
|
||||
progressContainer: {
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center"
|
||||
},
|
||||
card: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
padding: theme.spacing.unit * 2
|
||||
},
|
||||
root: {
|
||||
width: "100%",
|
||||
marginTop: theme.spacing.unit * 3
|
||||
},
|
||||
table: {
|
||||
minWidth: 500
|
||||
},
|
||||
wrapper: {
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
width: "100%"
|
||||
},
|
||||
tableWrapper: {
|
||||
overflowX: "auto"
|
||||
}
|
||||
});
|
||||
|
||||
class UserList extends React.Component {
|
||||
componentWillMount = () => {
|
||||
const { fetchUserList, meta } = this.props;
|
||||
const { page = 0, size = 10 } = meta || {};
|
||||
fetchUserList({ page, size });
|
||||
};
|
||||
|
||||
handleChangePage = (event, page) => {
|
||||
const { fetchUserList, meta = {} } = this.props;
|
||||
fetchUserList({ page, size: meta.size });
|
||||
};
|
||||
|
||||
handleChangeSize = event => {
|
||||
const { fetchUserList, meta = {} } = this.props;
|
||||
fetchUserList({ page: meta.page, size: event.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, isLoading, users, meta = {}, t } = this.props;
|
||||
const emptyRows = 0;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Screen>
|
||||
<div className={classes.progressContainer}>
|
||||
<CircularProgress className={classes.progress} />
|
||||
</div>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<div className={classes.wrapper}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography className={classes.title} color="textSecondary">
|
||||
{t(`user.listScreen.title`)}
|
||||
</Typography>
|
||||
<Typography variant="headline" component="h2">
|
||||
{t(`user.listScreen.headline`)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<div className={classes.tableWrapper}>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{t("user.label.id")}</TableCell>
|
||||
<TableCell>{t("user.label.username")}</TableCell>
|
||||
<TableCell>{t("user.label.edit")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.map(user => {
|
||||
return (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell>{user.id}</TableCell>
|
||||
<TableCell>{user.username}</TableCell>
|
||||
<TableCell>
|
||||
<Link to={`/user/edit/${user.id}`}>
|
||||
<CreateIcon />
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: 48 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
count={meta.totalElements}
|
||||
rowsPerPage={meta.size}
|
||||
page={meta.page}
|
||||
onChangePage={this.handleChangePage}
|
||||
onChangeRowsPerPage={this.handleChangeSize}
|
||||
Actions={ConnectedTablePaginationActionsWrapped}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const users = getItems(apiMethod.list)(entity.user)(state);
|
||||
const meta = getMeta(apiMethod.list)(entity.user)(state);
|
||||
const isFetchingUsers = getIsFetching(apiMethod.list)(entity.user)(state);
|
||||
const timeFetchedUsers = getTimeFetched(apiMethod.list)(entity.user)(state);
|
||||
|
||||
return {
|
||||
isLoading: !timeFetchedUsers || isFetchingUsers,
|
||||
isLoaded: !!timeFetchedUsers,
|
||||
users,
|
||||
meta
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchUserList: fetchList(entity.user)
|
||||
};
|
||||
|
||||
UserList.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const ConnectedUserList = connect(mapStateToProps, mapDispatchToProps)(
|
||||
UserList
|
||||
);
|
||||
|
||||
export default translate()(withStyles(styles)(ConnectedUserList));
|
||||
@@ -1,13 +1,21 @@
|
||||
export const apiMethod = {
|
||||
list: "list",
|
||||
all: "all",
|
||||
detail: "detail",
|
||||
create: "create",
|
||||
update: "update",
|
||||
delete: "delete"
|
||||
};
|
||||
|
||||
export const detailScreenType = {
|
||||
view: "view",
|
||||
edit: "edit",
|
||||
create: "create"
|
||||
};
|
||||
|
||||
export const apiHttpMethodMapping = {
|
||||
list: "get",
|
||||
all: "get",
|
||||
detail: "get",
|
||||
create: "post",
|
||||
update: "put",
|
||||
@@ -28,6 +36,9 @@ export const config = {
|
||||
ERROR: {
|
||||
NOCONNECTION: "NOCONNECTION",
|
||||
UNAUTHORIZED: "UNAUTHORIZED"
|
||||
},
|
||||
CONSTANTS: {
|
||||
FETCH_ALL_SIZE: 100
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ export default {
|
||||
hello: "Hello",
|
||||
title: "Mitgliederverwaltung",
|
||||
cancel: "Cancel",
|
||||
validation: {
|
||||
invalid_email_address: "Invalid Email Address",
|
||||
required: "Required",
|
||||
invalid_phone_number: "Invalid Phonenumber"
|
||||
},
|
||||
login: {
|
||||
button_login: "Login",
|
||||
label_username: "Email",
|
||||
@@ -18,10 +23,54 @@ export default {
|
||||
edit: "Edit",
|
||||
list: "List"
|
||||
},
|
||||
contact: {
|
||||
detailScreen: {
|
||||
headline: {
|
||||
create: "New Contact",
|
||||
edit: "Contact {{name}}",
|
||||
view: "Contact {{name}}"
|
||||
},
|
||||
saveButtonLabel: "Save",
|
||||
title: {
|
||||
create: "Create",
|
||||
edit: "Edit"
|
||||
}
|
||||
},
|
||||
listScreen: {
|
||||
title: "List",
|
||||
headline: "Contacts"
|
||||
},
|
||||
label: {
|
||||
email: "Email",
|
||||
firstname: "First Name",
|
||||
lastname: "Last Name",
|
||||
phone: "Phone",
|
||||
address: "Address",
|
||||
bankDetails: "Bank Details",
|
||||
groups: "Groups",
|
||||
edit: "Edit"
|
||||
},
|
||||
placeholder: {
|
||||
groups: "Groups"
|
||||
}
|
||||
},
|
||||
user_create_screen: {
|
||||
create_user: "Create User"
|
||||
},
|
||||
user: {
|
||||
listScreen: {
|
||||
title: "List",
|
||||
headline: "Users"
|
||||
},
|
||||
label: {
|
||||
id: "ID",
|
||||
username: "Email",
|
||||
groups: "Groups",
|
||||
edit: "Edit"
|
||||
},
|
||||
placeholder: {
|
||||
groups: "Groups"
|
||||
},
|
||||
cancel: "Cancel",
|
||||
delete: "Delete",
|
||||
error_message: "Oops! Something went wrong",
|
||||
@@ -41,6 +90,10 @@ export default {
|
||||
checkBoxEnabled: "Enabled"
|
||||
},
|
||||
role: {
|
||||
listScreen: {
|
||||
title: "List",
|
||||
headline: "Roles"
|
||||
},
|
||||
cancel: "Cancel",
|
||||
delete: "Delete",
|
||||
error_message: "Oops! Something went wrong",
|
||||
@@ -75,6 +128,10 @@ export default {
|
||||
delete: "Delete"
|
||||
},
|
||||
group: {
|
||||
listScreen: {
|
||||
title: "List",
|
||||
headline: "Groups"
|
||||
},
|
||||
cancel: "Cancel",
|
||||
delete: "Delete",
|
||||
error_message: "Oops! Something went wrong",
|
||||
|
||||
@@ -37,17 +37,18 @@ export const mergefetchRequest = action => state => {
|
||||
};
|
||||
export const mergefetchSuccess = action => state => {
|
||||
const { method, payload = {} } = action;
|
||||
const { data: { content, ...rest }, timeFetched } = payload;
|
||||
const { data: { content, meta }, timeFetched } = payload;
|
||||
const items = Array.isArray(content) ? content : [content];
|
||||
const enhancedItems = items.map(item => ({ ...item, timeFetched }));
|
||||
if (apiMethod[method]) {
|
||||
return {
|
||||
...state,
|
||||
items: unionBy(items, state.items, "id"),
|
||||
items: unionBy(enhancedItems, state.items, "id"),
|
||||
[method]: {
|
||||
...state[method],
|
||||
isFetching: false,
|
||||
items: items.map(item => item && item.id),
|
||||
meta: Array.isArray(content) ? rest : null,
|
||||
meta,
|
||||
timeFetched
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { apiActionType, apiMethod } from "../../config";
|
||||
import { apiActionType, apiMethod, config } from "../../config";
|
||||
|
||||
//API Actions
|
||||
export const CONNECTION_FAILURE = "CONNECTION_FAILURE";
|
||||
@@ -23,15 +23,33 @@ export const fetchGeneric = method => entity => payload => ({
|
||||
payload
|
||||
});
|
||||
export const FETCH_GENERIC_SUCCESS = FETCH_GENERIC(apiActionType.success);
|
||||
export const fetchGenericSuccess = method => entity => data => ({
|
||||
type: FETCH_GENERIC_SUCCESS,
|
||||
method,
|
||||
entity,
|
||||
payload: {
|
||||
timeFetched: new Date(),
|
||||
data
|
||||
}
|
||||
});
|
||||
export const fetchGenericSuccess = method => entity => data => {
|
||||
const { content, pageable, ...rest } = data;
|
||||
const meta = Array.isArray(content)
|
||||
? {
|
||||
size: pageable.pageSize,
|
||||
page: pageable.pageNumber,
|
||||
sort: pageable.sort,
|
||||
hasNext: !rest.last,
|
||||
first: rest.first,
|
||||
totalElements: rest.totalElements,
|
||||
totalPages: rest.totalPages
|
||||
}
|
||||
: null;
|
||||
const transformedData = {
|
||||
content: content ? content : data,
|
||||
meta
|
||||
};
|
||||
return {
|
||||
type: FETCH_GENERIC_SUCCESS,
|
||||
method,
|
||||
entity,
|
||||
payload: {
|
||||
timeFetched: new Date(),
|
||||
data: transformedData
|
||||
}
|
||||
};
|
||||
};
|
||||
export const FETCH_GENERIC_FAILURE = FETCH_GENERIC(apiActionType.failure);
|
||||
export const fetchGenericFailure = method => entity => error => ({
|
||||
type: FETCH_GENERIC_FAILURE,
|
||||
@@ -47,6 +65,13 @@ export const fetchList = entity => parameters =>
|
||||
fetchGeneric(apiMethod.list)(entity)({ parameters });
|
||||
export const fetchListSuccess = fetchGenericSuccess(apiMethod.list);
|
||||
export const fetchListFailure = fetchGenericFailure(apiMethod.list);
|
||||
export const fetchAll = entity => (page = 0) =>
|
||||
fetchGeneric(apiMethod.all)(entity)({
|
||||
parameters: {
|
||||
size: config.CONSTANTS.FETCH_ALL_SIZE,
|
||||
page
|
||||
}
|
||||
});
|
||||
export const fetchDetail = fetchGeneric(apiMethod.detail);
|
||||
export const fetchDetailById = entity => id =>
|
||||
fetchGeneric(apiMethod.detail)(entity)({ data: { id } });
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
fetchLoginFailure,
|
||||
reduxRehydrationCompleted,
|
||||
fetchGenericSuccess,
|
||||
fetchGenericFailure
|
||||
fetchGenericFailure,
|
||||
fetchAll
|
||||
} from "../actions";
|
||||
import {
|
||||
LOGOUT,
|
||||
@@ -77,6 +78,8 @@ export function* fetchSaga(api, action) {
|
||||
};
|
||||
if (method === apiMethod.list) {
|
||||
// no todo yet
|
||||
} else if (method === apiMethod.all) {
|
||||
// no todo yet
|
||||
} else if (method === apiMethod.detail) {
|
||||
request.endpoint = `${entity.endpoint}/${data.id}`;
|
||||
} else if (method === apiMethod.create) {
|
||||
@@ -116,10 +119,16 @@ export function* fetchSaga(api, action) {
|
||||
}
|
||||
|
||||
export function* fetchSuccessSaga(action) {
|
||||
const { method, entity } = action;
|
||||
const { method, entity, payload = {} } = action;
|
||||
const { data: { meta } } = payload || {};
|
||||
if (method === apiMethod.delete || method === apiMethod.create) {
|
||||
yield put(push(`/${entity.name}/list`));
|
||||
}
|
||||
if (method === apiMethod.all) {
|
||||
if (meta && meta.hasNext && (meta.page || meta.page === 0)) {
|
||||
yield put(fetchAll(entity)(meta.page + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchLoginSuccessCallback(data) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { entity } from "../../lib/entity";
|
||||
import { apiMethod } from "../../config";
|
||||
|
||||
export const getContacts = getItems(apiMethod.list)(entity.contact);
|
||||
export const getContactById = getItemById(apiMethod.list)(entity.contact);
|
||||
export const getContactById = getItemById(entity.contact);
|
||||
export const isFetchingContactList = getIsFetching(apiMethod.list)(
|
||||
entity.contact
|
||||
);
|
||||
|
||||
@@ -18,7 +18,8 @@ export const getError = getEntityMethodValue("error");
|
||||
export const getTimeFetched = getEntityMethodValue("timeFetched");
|
||||
export const getIsFetching = getEntityMethodValue("isFetching");
|
||||
|
||||
export const getItemById = method => entity => id => state => {
|
||||
const items = getItems(method)(entity)(state);
|
||||
return find(items, item => item.id === id);
|
||||
export const getItemById = entity => id => state => {
|
||||
const parsedId = parseInt(id, 10);
|
||||
const items = getEntityItems(entity)(state);
|
||||
return find(items, item => item.id === parsedId);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ export const getFormValues = formName => state =>
|
||||
export const getLoginFormValues = state => getFormValues("login")(state);
|
||||
export const getUserFormValues = state =>
|
||||
getFormValues("UserCreateForm")(state);
|
||||
export const getContactFormValues = state => getFormValues("contact")(state);
|
||||
export const getGroupFormValues = state =>
|
||||
getFormValues("GroupCreateForm")(state);
|
||||
export const getRoleFormValues = state =>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { entity } from "../../lib/entity";
|
||||
import { getItems, getItemById } from "./entitySelectors";
|
||||
|
||||
export const getGroups = getItems(apiMethod.list)(entity.group);
|
||||
export const getGroupById = getItemById(apiMethod.list)(entity.group);
|
||||
export const getGroupById = getItemById(entity.group);
|
||||
|
||||
export const isLoadedGroups = state =>
|
||||
!get(state, "group.list.isFetching", false) &&
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from "./permissionSelectors";
|
||||
export * from "./roleSelectors";
|
||||
export * from "./userSelectors";
|
||||
export * from "./groupSelectors";
|
||||
export * from "./contactsSelectors";
|
||||
|
||||
@@ -1,2 +1,14 @@
|
||||
import { get } from "lodash";
|
||||
import { getItemById } from ".";
|
||||
import { entity } from "../../lib/entity";
|
||||
|
||||
export const getLogin = (key = "isLoggedIn") => state => state.login[key];
|
||||
export const getLoginError = state => getLogin("error")(state);
|
||||
|
||||
export const getUserIdFromToken = state => get(state, "login.decodedJwt.id");
|
||||
export const getContactIdFromToken = state => {
|
||||
const userId = getUserIdFromToken(state);
|
||||
if (!userId) return undefined;
|
||||
const user = getItemById(entity.user)(userId)(state);
|
||||
return (user && user.contact) || undefined;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { entity } from "../../lib/entity";
|
||||
import { getItems, getItemById } from "./entitySelectors";
|
||||
|
||||
export const getPermissions = getItems(apiMethod.list)(entity.permission);
|
||||
export const getPermissionById = getItemById(apiMethod.list)(entity.permission);
|
||||
export const getPermissionById = getItemById(entity.permission);
|
||||
|
||||
export const isFetchingPermissions = state =>
|
||||
!!get(state, `${entity.permission.name}.list.isFetching`);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { entity } from "../../lib/entity";
|
||||
import { getItems, getItemById } from "./entitySelectors";
|
||||
|
||||
export const getRoles = getItems(apiMethod.list)(entity.role);
|
||||
export const getRoleById = getItemById(apiMethod.list)(entity.role);
|
||||
export const getRoleById = getItemById(entity.role);
|
||||
|
||||
export const getCreateRole = state => get(state, "role.create");
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { apiMethod } from "../../config";
|
||||
import { get } from "lodash";
|
||||
|
||||
export const getUsers = getItems(apiMethod.list)(entity.user);
|
||||
export const getUserById = getItemById(apiMethod.list)(entity.user);
|
||||
export const getUserById = getItemById(entity.user);
|
||||
export const isFetchingUserList = getIsFetching(apiMethod.list)(entity.user);
|
||||
export const getTimeFetchedUserList = getTimeFetched(apiMethod.list)(
|
||||
entity.user
|
||||
|
||||
51
yarn.lock
51
yarn.lock
@@ -24,9 +24,9 @@
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.0.tgz#b85a0bcf1e1cc84eb4901b7e96966aedc6f078d1"
|
||||
|
||||
"@types/react-transition-group@^2.0.6":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.0.7.tgz#2847292d54c5685d982ae5a3ecb6960946689d87"
|
||||
"@types/react-transition-group@^2.0.8":
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.0.8.tgz#1ea86f6d8288e4bba8d743317ba9cc61cdacc1ad"
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
@@ -3453,6 +3453,10 @@ hoist-non-react-statics@2.3.1, hoist-non-react-statics@^2.2.1, hoist-non-react-s
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
||||
|
||||
hoist-non-react-statics@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||
@@ -5086,18 +5090,18 @@ material-ui-icons@^1.0.0-beta.17:
|
||||
dependencies:
|
||||
recompose "^0.26.0"
|
||||
|
||||
material-ui@^1.0.0-beta.35:
|
||||
version "1.0.0-beta.35"
|
||||
resolved "https://registry.yarnpkg.com/material-ui/-/material-ui-1.0.0-beta.35.tgz#eeac6be307c0469943c7686e5c6bd4eaa6b1b563"
|
||||
material-ui@^1.0.0-beta.43:
|
||||
version "1.0.0-beta.43"
|
||||
resolved "https://registry.yarnpkg.com/material-ui/-/material-ui-1.0.0-beta.43.tgz#21074fd0ef5f1735a54060dbfd060e6d46fd5ef5"
|
||||
dependencies:
|
||||
"@types/jss" "^9.3.0"
|
||||
"@types/react-transition-group" "^2.0.6"
|
||||
"@types/react-transition-group" "^2.0.8"
|
||||
babel-runtime "^6.26.0"
|
||||
brcast "^3.0.1"
|
||||
classnames "^2.2.5"
|
||||
deepmerge "^2.0.1"
|
||||
dom-helpers "^3.2.1"
|
||||
hoist-non-react-statics "^2.3.1"
|
||||
hoist-non-react-statics "^2.5.0"
|
||||
jss "^9.3.3"
|
||||
jss-camel-case "^6.0.0"
|
||||
jss-default-unit "^8.0.2"
|
||||
@@ -5111,7 +5115,8 @@ material-ui@^1.0.0-beta.35:
|
||||
prop-types "^15.6.0"
|
||||
react-event-listener "^0.5.1"
|
||||
react-jss "^8.1.0"
|
||||
react-popper "^0.8.0"
|
||||
react-lifecycles-compat "^2.0.0"
|
||||
react-popper "^0.10.0"
|
||||
react-scrollbar-size "^2.0.2"
|
||||
react-transition-group "^2.2.1"
|
||||
recompose "^0.26.0"
|
||||
@@ -5923,9 +5928,9 @@ pn@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
||||
|
||||
popper.js@^1.12.9:
|
||||
version "1.12.9"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.12.9.tgz#0dfbc2dff96c451bb332edcfcfaaf566d331d5b3"
|
||||
popper.js@^1.14.1:
|
||||
version "1.14.3"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
|
||||
|
||||
portfinder@^1.0.9:
|
||||
version "1.0.13"
|
||||
@@ -6304,6 +6309,14 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.5.9,
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
prop-types@^15.6.1:
|
||||
version "15.6.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
proxy-addr@~2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
|
||||
@@ -6510,18 +6523,22 @@ react-jss@^8.1.0:
|
||||
prop-types "^15.6.0"
|
||||
theming "^1.3.0"
|
||||
|
||||
react-lifecycles-compat@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-2.0.2.tgz#00a23160eec17a43b94dd74f95d44a1a2c3c5ec1"
|
||||
|
||||
react-maskinput@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-maskinput/-/react-maskinput-1.0.0.tgz#4d4e4c42cadd289c219256c34fd741ef552ba830"
|
||||
dependencies:
|
||||
input-core "^1.0.0"
|
||||
|
||||
react-popper@^0.8.0:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.8.2.tgz#092095ff13933211d3856d9f325511ec3a42f12c"
|
||||
react-popper@^0.10.0:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.10.1.tgz#6a1f2595faffda77105bed4e89ecf22607a4c452"
|
||||
dependencies:
|
||||
popper.js "^1.12.9"
|
||||
prop-types "^15.6.0"
|
||||
popper.js "^1.14.1"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-reconciler@^0.7.0:
|
||||
version "0.7.0"
|
||||
|
||||
Reference in New Issue
Block a user