Show form errors

This commit is contained in:
Manuel Bouza
2019-02-13 14:22:50 +01:00
parent eb154be04a
commit 4d77ddb2aa
6 changed files with 48 additions and 12 deletions

View File

@@ -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",

View File

@@ -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;

View File

@@ -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}

View File

@@ -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>

View File

@@ -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)}
/> />
) )
} }

View File

@@ -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==