2018-09-20 11:58:47 -07:00
|
|
|
import React from 'react';
|
|
|
|
import { Form, Input, DatePicker, Card, Icon, Alert, Checkbox, Button } from 'antd';
|
|
|
|
import moment from 'moment';
|
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: '',
|
|
|
|
dateEstimated: '',
|
|
|
|
payoutPercent: '100',
|
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;
|
|
|
|
const lastMilestone = oldMilestones[oldMilestones.length - 1];
|
2018-11-14 08:43:00 -08:00
|
|
|
const halfPayout = parseInt(lastMilestone.payoutPercent, 10) / 2;
|
2018-09-20 11:58:47 -07:00
|
|
|
const milestones = [
|
|
|
|
...oldMilestones,
|
|
|
|
{
|
|
|
|
...DEFAULT_STATE.milestones[0],
|
2018-11-14 08:43:00 -08:00
|
|
|
payoutPercent: halfPayout.toString(),
|
2018-09-20 11:58:47 -07:00
|
|
|
},
|
|
|
|
];
|
|
|
|
milestones[milestones.length - 2] = {
|
|
|
|
...lastMilestone,
|
2018-11-14 08:43:00 -08:00
|
|
|
payoutPercent: halfPayout.toString(),
|
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]}
|
|
|
|
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;
|
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"
|
|
|
|
placeholder="Title"
|
|
|
|
type="text"
|
|
|
|
name="title"
|
|
|
|
value={milestone.title}
|
|
|
|
onChange={ev => onChange(index, { ...milestone, title: ev.currentTarget.value })}
|
|
|
|
/>
|
|
|
|
<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}
|
|
|
|
name="body"
|
|
|
|
placeholder="Description of the deliverable"
|
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
|
|
|
}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex' }}>
|
|
|
|
<DatePicker.MonthPicker
|
|
|
|
style={{ flex: 1, marginRight: '0.5rem' }}
|
|
|
|
placeholder="Expected completion date"
|
2018-11-14 08:43:00 -08:00
|
|
|
value={
|
|
|
|
milestone.dateEstimated
|
|
|
|
? moment(milestone.dateEstimated, 'MMMM YYYY')
|
|
|
|
: undefined
|
|
|
|
}
|
2018-09-20 11:58:47 -07:00
|
|
|
format="MMMM YYYY"
|
|
|
|
allowClear={false}
|
2018-11-14 08:43:00 -08:00
|
|
|
onChange={(_, dateEstimated) => onChange(index, { ...milestone, dateEstimated })}
|
2018-09-20 11:58:47 -07:00
|
|
|
/>
|
|
|
|
<Input
|
|
|
|
min={1}
|
|
|
|
max={100}
|
|
|
|
type="number"
|
|
|
|
value={milestone.payoutPercent}
|
|
|
|
onChange={ev =>
|
|
|
|
onChange(index, {
|
|
|
|
...milestone,
|
2018-11-14 08:43:00 -08:00
|
|
|
payoutPercent: ev.currentTarget.value || '0',
|
2018-09-20 11:58:47 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
addonAfter="%"
|
|
|
|
style={{ maxWidth: '120px', width: '100%' }}
|
|
|
|
/>
|
|
|
|
{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>
|
|
|
|
);
|