Show form errors
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
"date-fns": "^1.30.1",
|
"date-fns": "^1.30.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"mobx": "^5.5.0",
|
"mobx": "^5.5.0",
|
||||||
|
|||||||
@@ -24,6 +24,16 @@
|
|||||||
color: #eee;
|
color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-error {
|
||||||
|
input, textarea {
|
||||||
|
border-color: #FB3A2F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-error {
|
||||||
|
color: #FB3A2F;
|
||||||
|
}
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: table;
|
display: table;
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ class Bubble extends Component {
|
|||||||
.createActivity(this.changesetWithDefaults)
|
.createActivity(this.changesetWithDefaults)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.close()
|
this.close()
|
||||||
|
this.changeset = {}
|
||||||
this.formErrors = {}
|
this.formErrors = {}
|
||||||
})
|
})
|
||||||
.catch(this.handleSubmitError)
|
.catch(this.handleSubmitError)
|
||||||
@@ -173,6 +174,7 @@ class Bubble extends Component {
|
|||||||
<Form
|
<Form
|
||||||
projects={this.projects}
|
projects={this.projects}
|
||||||
changeset={this.changesetWithDefaults}
|
changeset={this.changesetWithDefaults}
|
||||||
|
errors={this.formErrors}
|
||||||
isLoading={this.isLoading}
|
isLoading={this.isLoading}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onSubmit={this.handleSubmit}
|
onSubmit={this.handleSubmit}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from "react"
|
import React, { Component } from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import Select from "components/Select"
|
import Select from "components/Select"
|
||||||
|
import cn from "classnames"
|
||||||
|
|
||||||
class Form extends Component {
|
class Form extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -10,6 +11,7 @@ class Form extends Component {
|
|||||||
task: PropTypes.object,
|
task: PropTypes.object,
|
||||||
hours: PropTypes.string
|
hours: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
errors: PropTypes.object,
|
||||||
projects: PropTypes.array.isRequired,
|
projects: PropTypes.array.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired
|
onSubmit: PropTypes.func.isRequired
|
||||||
@@ -33,29 +35,39 @@ class Form extends Component {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { projects, changeset, onChange, onSubmit } = this.props
|
const { projects, changeset, errors, onChange, onSubmit } = this.props
|
||||||
const project = Select.findOptionByValue(projects, changeset.assignment_id)
|
const project = Select.findOptionByValue(projects, changeset.assignment_id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className="form-group">
|
<div
|
||||||
|
className={cn("form-group", { "has-error": errors.assignment_id })}
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
name="assignment_id"
|
name="assignment_id"
|
||||||
options={projects}
|
options={projects}
|
||||||
value={changeset.assignment_id}
|
value={changeset.assignment_id}
|
||||||
|
hasError={!!errors.assignment_id}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
|
{errors.assignment_id ? (
|
||||||
|
<div className="form-error">{errors.assignment_id.join('; ')}</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className={cn("form-group", { "has-error": errors.task_id })}>
|
||||||
<Select
|
<Select
|
||||||
name="task_id"
|
name="task_id"
|
||||||
options={project?.tasks || []}
|
options={project?.tasks || []}
|
||||||
value={changeset.task_id}
|
value={changeset.task_id}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
hasError={!!errors.task_id}
|
||||||
noOptionsMessage={() => "Zuerst Projekt wählen"}
|
noOptionsMessage={() => "Zuerst Projekt wählen"}
|
||||||
/>
|
/>
|
||||||
|
{errors.task_id ? (
|
||||||
|
<div className="form-error">{errors.task_id.join('; ')}</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className={cn("form-group", { "has-error": errors.hours })}>
|
||||||
<input
|
<input
|
||||||
name="hours"
|
name="hours"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
@@ -65,8 +77,11 @@ class Form extends Component {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
{errors.hours ? (
|
||||||
|
<div className="form-error">{errors.hours.join('; ')}</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className={cn("form-group", { "has-error": errors.description })}>
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -74,6 +89,9 @@ class Form extends Component {
|
|||||||
placeholder="Beschreibung der Tätigkeit - mind. 3 Zeichen"
|
placeholder="Beschreibung der Tätigkeit - mind. 3 Zeichen"
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
|
{errors.description ? (
|
||||||
|
<div className="form-error">{errors.description.join('; ')}</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button disabled={!this.isValid()}>Speichern</button>
|
<button disabled={!this.isValid()}>Speichern</button>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
flatMap,
|
flatMap,
|
||||||
pathEq
|
pathEq
|
||||||
} from "lodash/fp"
|
} from "lodash/fp"
|
||||||
|
import { trace } from "utils"
|
||||||
|
|
||||||
const customTheme = theme => ({
|
const customTheme = theme => ({
|
||||||
...theme,
|
...theme,
|
||||||
@@ -23,7 +24,11 @@ const customTheme = theme => ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const customStyles = {
|
const customStyles = props => ({
|
||||||
|
control: (base, _state) => ({
|
||||||
|
...base,
|
||||||
|
borderColor: props.hasError ? "#FB3A2F" : base.borderColor
|
||||||
|
}),
|
||||||
groupHeading: (base, _state) => ({
|
groupHeading: (base, _state) => ({
|
||||||
...base,
|
...base,
|
||||||
color: "black",
|
color: "black",
|
||||||
@@ -31,7 +36,7 @@ const customStyles = {
|
|||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
fontSize: "100%"
|
fontSize: "100%"
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
const filterOption = createFilter({
|
const filterOption = createFilter({
|
||||||
stringify: compose(
|
stringify: compose(
|
||||||
@@ -47,10 +52,10 @@ export default class Select extends Component {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
|
hasError: PropTypes.bool,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static findOptionByValue = (selectOptions, value) => {
|
static findOptionByValue = (selectOptions, value) => {
|
||||||
const options = flatMap(
|
const options = flatMap(
|
||||||
option => (option.options ? option.options : option),
|
option => (option.options ? option.options : option),
|
||||||
@@ -58,7 +63,7 @@ export default class Select extends Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return options.find(pathEq("value", value))
|
return options.find(pathEq("value", value))
|
||||||
}
|
};
|
||||||
|
|
||||||
handleChange = option => {
|
handleChange = option => {
|
||||||
const { name, onChange } = this.props
|
const { name, onChange } = this.props
|
||||||
@@ -67,7 +72,7 @@ export default class Select extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value, ...passThroughProps } = this.props
|
const { value, hasError, ...passThroughProps } = this.props
|
||||||
return (
|
return (
|
||||||
<ReactSelect
|
<ReactSelect
|
||||||
{...passThroughProps}
|
{...passThroughProps}
|
||||||
@@ -75,7 +80,7 @@ export default class Select extends Component {
|
|||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
filterOption={filterOption}
|
filterOption={filterOption}
|
||||||
theme={customTheme}
|
theme={customTheme}
|
||||||
styles={customStyles}
|
styles={customStyles(this.props)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1709,7 +1709,7 @@ class-utils@^0.3.5:
|
|||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@^2.2.5:
|
classnames@^2.2.5, classnames@^2.2.6:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||||
|
|||||||
Reference in New Issue
Block a user