2018-09-20 11:58:47 -07:00
|
|
|
import React from 'react';
|
2019-11-13 14:38:17 -08:00
|
|
|
import { Form, Input, Card, Icon, Alert, Checkbox, Button } from 'antd';
|
2018-11-14 08:43:00 -08:00
|
|
|
import { ProposalDraft, CreateMilestone } from 'types';
|
2018-09-20 11:58:47 -07:00
|
|
|
import { getCreateErrors } from 'modules/create/utils';
|
|
|
|
|
|
|
|
interface State {
|
2018-11-14 08:43:00 -08:00
|
|
|
milestones: ProposalDraft['milestones'];
|
2018-09-20 11:58:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
initialState: Partial<State>;
|
2018-11-14 08:43:00 -08:00
|
|
|
updateForm(form: Partial<ProposalDraft>): void;
|
2018-09-20 11:58:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const DEFAULT_STATE: State = {
|
|
|
|
milestones: [
|
|
|
|
{
|
|
|
|
title: '',
|
2018-11-14 08:43:00 -08:00
|
|
|
content: '',
|
2019-11-13 14:38:17 -08:00
|
|
|
daysEstimated: '',
|
2019-02-19 13:42:40 -08:00
|
|
|
payoutPercent: '',
|
2018-09-20 11:58:47 -07:00
|
|
|
immediatePayout: false,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
|
|
|
export default class CreateFlowMilestones extends React.Component<Props, State> {
|
|
|
|
constructor(props: Props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
...DEFAULT_STATE,
|
|
|
|
...(props.initialState || {}),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Don't allow for empty milestones array
|
|
|
|
if (!this.state.milestones.length) {
|
|
|
|
this.state = {
|
|
|
|
...this.state,
|
|
|
|
milestones: [...DEFAULT_STATE.milestones],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 21:27:02 -07:00
|
|
|
handleMilestoneChange = (index: number, milestone: CreateMilestone) => {
|
2018-09-20 11:58:47 -07:00
|
|
|
const milestones = [...this.state.milestones];
|
|
|
|
milestones[index] = milestone;
|
|
|
|
this.setState({ milestones }, () => {
|
|
|
|
this.props.updateForm(this.state);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
addMilestone = () => {
|
|
|
|
const { milestones: oldMilestones } = this.state;
|
2019-02-19 13:42:40 -08:00
|
|
|
const milestones = [...oldMilestones, { ...DEFAULT_STATE.milestones[0] }];
|
2018-09-20 11:58:47 -07:00
|
|
|
this.setState({ milestones });
|
|
|
|
};
|
|
|
|
|
|
|
|
removeMilestone = (index: number) => {
|
|
|
|
let milestones = this.state.milestones.filter((_, i) => i !== index);
|
|
|
|
if (milestones.length === 0) {
|
|
|
|
milestones = [...DEFAULT_STATE.milestones];
|
|
|
|
}
|
|
|
|
this.setState({ milestones }, () => {
|
|
|
|
this.props.updateForm(this.state);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { milestones } = this.state;
|
|
|
|
const errors = getCreateErrors(this.state, true);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Form layout="vertical" style={{ maxWidth: 720, margin: '0 auto' }}>
|
|
|
|
{milestones.map((milestone, idx) => (
|
|
|
|
<MilestoneFields
|
|
|
|
key={idx}
|
|
|
|
milestone={milestone}
|
|
|
|
index={idx}
|
|
|
|
error={errors.milestones && errors.milestones[idx]}
|
2019-11-13 14:38:17 -08:00
|
|
|
|
2018-09-20 11:58:47 -07:00
|
|
|
onChange={this.handleMilestoneChange}
|
|
|
|
onRemove={this.removeMilestone}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
|
|
|
|
{milestones.length < 10 && (
|
|
|
|
<Button type="dashed" onClick={this.addMilestone}>
|
|
|
|
<Icon type="plus" /> Add another milestone
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</Form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface MilestoneFieldsProps {
|
|
|
|
index: number;
|
2018-10-04 21:27:02 -07:00
|
|
|
milestone: CreateMilestone;
|
2019-11-13 14:38:17 -08:00
|
|
|
// previousMilestoneDateEstimate: moment.Moment | undefined;
|
2018-10-19 15:03:37 -07:00
|
|
|
error: Falsy | string;
|
2018-10-04 21:27:02 -07:00
|
|
|
onChange(index: number, milestone: CreateMilestone): void;
|
2018-09-20 11:58:47 -07:00
|
|
|
onRemove(index: number): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const MilestoneFields = ({
|
|
|
|
index,
|
|
|
|
milestone,
|
|
|
|
error,
|
|
|
|
onChange,
|
|
|
|
onRemove,
|
|
|
|
}: MilestoneFieldsProps) => (
|
|
|
|
<Card style={{ marginBottom: '2rem' }}>
|
|
|
|
<div style={{ display: 'flex', marginBottom: '0.5rem', alignItems: 'center' }}>
|
|
|
|
<Input
|
|
|
|
size="large"
|
2019-02-19 13:42:40 -08:00
|
|
|
placeholder="Milestone title"
|
2018-09-20 11:58:47 -07:00
|
|
|
type="text"
|
|
|
|
name="title"
|
|
|
|
value={milestone.title}
|
|
|
|
onChange={ev => onChange(index, { ...milestone, title: ev.currentTarget.value })}
|
2019-03-18 11:47:46 -07:00
|
|
|
maxLength={80}
|
2018-09-20 11:58:47 -07:00
|
|
|
/>
|
|
|
|
<button
|
|
|
|
onClick={() => onRemove(index)}
|
|
|
|
style={{
|
|
|
|
paddingLeft: '0.5rem',
|
|
|
|
fontSize: '1.5rem',
|
|
|
|
cursor: 'pointer',
|
|
|
|
opacity: 0.8,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon type="close-circle-o" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '0.5rem' }}>
|
|
|
|
<Input.TextArea
|
|
|
|
rows={3}
|
2018-11-14 09:59:48 -08:00
|
|
|
name="content"
|
2019-02-19 13:42:40 -08:00
|
|
|
placeholder="Description of what will be delivered"
|
2018-11-14 08:43:00 -08:00
|
|
|
value={milestone.content}
|
2018-09-20 11:58:47 -07:00
|
|
|
onChange={ev =>
|
2018-11-14 08:43:00 -08:00
|
|
|
onChange(index, { ...milestone, content: ev.currentTarget.value })
|
2018-09-20 11:58:47 -07:00
|
|
|
}
|
2019-03-18 11:47:46 -07:00
|
|
|
maxLength={255}
|
2018-09-20 11:58:47 -07:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex' }}>
|
2019-11-13 14:38:17 -08:00
|
|
|
<Input
|
|
|
|
value={milestone.daysEstimated}
|
|
|
|
disabled={milestone.immediatePayout}
|
|
|
|
placeholder="Estimated days to complete"
|
|
|
|
onChange={ev =>{
|
|
|
|
return onChange(index, {
|
|
|
|
...milestone,
|
|
|
|
daysEstimated: ev.currentTarget.value
|
|
|
|
})
|
2019-02-05 13:25:58 -08:00
|
|
|
}
|
2019-07-24 11:29:11 -07:00
|
|
|
}
|
2019-11-13 14:38:17 -08:00
|
|
|
addonAfter="days"
|
|
|
|
style={{ flex: 1, marginRight: '0.5rem' }}
|
|
|
|
maxLength={6}
|
2018-09-20 11:58:47 -07:00
|
|
|
/>
|
|
|
|
<Input
|
|
|
|
value={milestone.payoutPercent}
|
2019-02-19 13:42:40 -08:00
|
|
|
placeholder="Payout"
|
2018-09-20 11:58:47 -07:00
|
|
|
onChange={ev =>
|
|
|
|
onChange(index, {
|
|
|
|
...milestone,
|
2019-02-19 13:42:40 -08:00
|
|
|
payoutPercent: ev.currentTarget.value,
|
2018-09-20 11:58:47 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
addonAfter="%"
|
|
|
|
style={{ maxWidth: '120px', width: '100%' }}
|
2019-03-18 11:47:46 -07:00
|
|
|
maxLength={6}
|
2018-09-20 11:58:47 -07:00
|
|
|
/>
|
|
|
|
{index === 0 && (
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '0.5rem' }}>
|
|
|
|
<Checkbox
|
|
|
|
checked={milestone.immediatePayout}
|
|
|
|
onChange={ev =>
|
|
|
|
onChange(index, {
|
|
|
|
...milestone,
|
|
|
|
immediatePayout: ev.target.checked,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<span style={{ opacity: 0.7 }}>Payout Immediately</span>
|
|
|
|
</Checkbox>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
<Alert style={{ marginTop: '1rem' }} type="error" message={error} showIcon />
|
|
|
|
)}
|
|
|
|
</Card>
|
|
|
|
);
|