finish new responsive contact form
This commit is contained in:
@@ -66,11 +66,6 @@ class App extends Component {
|
||||
path="/contact/:id/:type"
|
||||
component={ContactDetailScreen}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/contact/:id/:type"
|
||||
component={ContactDetailScreen}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/contact/:type"
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { withStyles, Card, CardContent, Typography } from "material-ui";
|
||||
import {
|
||||
withStyles,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
CircularProgress
|
||||
} from "material-ui";
|
||||
import PropTypes from "prop-types";
|
||||
import { initialize } from "redux-form";
|
||||
import { get } from "lodash";
|
||||
@@ -10,18 +16,34 @@ import { goBack } from "react-router-redux";
|
||||
import {
|
||||
getContactById,
|
||||
getEntityItems,
|
||||
getContactFormValues
|
||||
getContactFormValues,
|
||||
getIsFetching
|
||||
} from "../../redux/selectors";
|
||||
import { entity } from "../../lib/entity";
|
||||
import { detailScreenType } from "../../config";
|
||||
import { detailScreenType, apiMethod } from "../../config";
|
||||
|
||||
import Screen from "../Screen";
|
||||
import ContactForm from "./ContactForm";
|
||||
import { fetchUpdateByObject, fetchCreateByObject } from "../../redux/actions";
|
||||
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: {
|
||||
minWidth: 275
|
||||
height: "100%"
|
||||
},
|
||||
bullet: {
|
||||
display: "inline-block",
|
||||
@@ -38,15 +60,59 @@ const styles = theme => ({
|
||||
});
|
||||
|
||||
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);
|
||||
fetchUpdateByObject({
|
||||
...values,
|
||||
groups: multiGroups
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (type === detailScreenType.create) {
|
||||
fetchCreateByObject(values);
|
||||
fetchCreateByObject({
|
||||
...values,
|
||||
groups: multiGroups
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -59,7 +125,22 @@ export class DetailScreen extends Component {
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const { classes, t, contact, name, type } = this.props;
|
||||
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>
|
||||
@@ -78,6 +159,9 @@ export class DetailScreen extends Component {
|
||||
enableReinitialize={true}
|
||||
onSubmit={this.handleSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
optionsGroups={optionsGroups}
|
||||
multiGroups={multiGroups}
|
||||
handleChangeMultiGroups={this.handleChangeMultiGroups}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -92,9 +176,15 @@ const mapStateToProps = (state, ownProps) => {
|
||||
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,
|
||||
@@ -112,7 +202,9 @@ const mapDispatchToProps = {
|
||||
initialize,
|
||||
goBack,
|
||||
fetchCreateByObject: fetchCreateByObject(entity.contact),
|
||||
fetchUpdateByObject: fetchUpdateByObject(entity.contact)
|
||||
fetchUpdateByObject: fetchUpdateByObject(entity.contact),
|
||||
fetchContactDetailById: fetchDetailById(entity.contact),
|
||||
fetchAllGroups: fetchAll(entity.group)
|
||||
};
|
||||
|
||||
DetailScreen.propTypes = {
|
||||
|
||||
@@ -6,6 +6,7 @@ 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: {
|
||||
@@ -16,6 +17,9 @@ const styles = theme => ({
|
||||
},
|
||||
actions: {
|
||||
width: "100%"
|
||||
},
|
||||
formItem: {
|
||||
width: "100%"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,20 +28,17 @@ export class ContactForm extends Component {
|
||||
const {
|
||||
handleSubmit,
|
||||
onCancel,
|
||||
pristine,
|
||||
invalid,
|
||||
submitting,
|
||||
asyncValidating,
|
||||
classes,
|
||||
t
|
||||
t,
|
||||
optionsGroups = [],
|
||||
handleChangeMultiGroups,
|
||||
multiGroups
|
||||
} = this.props;
|
||||
console.log(this.props);
|
||||
const canSubmit = !(
|
||||
pristine ||
|
||||
submitting ||
|
||||
invalid ||
|
||||
asyncValidating === true
|
||||
);
|
||||
const canSubmit = !(submitting || invalid || asyncValidating === true);
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} className={classes.form}>
|
||||
<CardContent>
|
||||
@@ -82,6 +83,18 @@ export class ContactForm extends Component {
|
||||
name="bankDetails"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
{/*TODO: fix the reactselect component to work with redux-forms.*/}
|
||||
<ReactSelect
|
||||
className={classes.formItem}
|
||||
name={"selectGroups"}
|
||||
options={optionsGroups}
|
||||
handleChangeMulti={handleChangeMultiGroups}
|
||||
multi={multiGroups}
|
||||
canAddMultipleValues={true}
|
||||
placeholder={t("contact.placeholder.groups")}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions className={classes.actions}>
|
||||
@@ -115,7 +128,8 @@ ContactForm.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool
|
||||
submitting: PropTypes.bool,
|
||||
groups: PropTypes.array
|
||||
};
|
||||
|
||||
export default reduxForm({ form: "contact" })(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const apiMethod = {
|
||||
list: "list",
|
||||
all: "all",
|
||||
detail: "detail",
|
||||
create: "create",
|
||||
update: "update",
|
||||
@@ -14,6 +15,7 @@ export const detailScreenType = {
|
||||
|
||||
export const apiHttpMethodMapping = {
|
||||
list: "get",
|
||||
all: "get",
|
||||
detail: "get",
|
||||
create: "post",
|
||||
update: "put",
|
||||
@@ -34,6 +36,9 @@ export const config = {
|
||||
ERROR: {
|
||||
NOCONNECTION: "NOCONNECTION",
|
||||
UNAUTHORIZED: "UNAUTHORIZED"
|
||||
},
|
||||
CONSTANTS: {
|
||||
FETCH_ALL_SIZE: 100
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ export default {
|
||||
},
|
||||
saveButtonLabel: "Save",
|
||||
title: {
|
||||
create: "Create"
|
||||
create: "Create",
|
||||
edit: "Edit"
|
||||
}
|
||||
},
|
||||
label: {
|
||||
@@ -43,6 +44,9 @@ export default {
|
||||
address: "Address",
|
||||
bankDetails: "Bank Details",
|
||||
groups: "Groups"
|
||||
},
|
||||
placeholder: {
|
||||
groups: "Groups"
|
||||
}
|
||||
},
|
||||
user_create_screen: {
|
||||
|
||||
@@ -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)
|
||||
? {
|
||||
pageSize: pageable.pageSize,
|
||||
pageNUmber: 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) {
|
||||
|
||||
@@ -19,6 +19,7 @@ export const getTimeFetched = getEntityMethodValue("timeFetched");
|
||||
export const getIsFetching = getEntityMethodValue("isFetching");
|
||||
|
||||
export const getItemById = entity => id => state => {
|
||||
const parsedId = parseInt(id, 10);
|
||||
const items = getEntityItems(entity)(state);
|
||||
return find(items, item => item.id === id);
|
||||
return find(items, item => item.id === parsedId);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user