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