22f488a7 |
import React from 'react';
import Box from './Box';
import uuid from 'uuid/v4';
import {
SortableContainer,
SortableElement,
arrayMove,
} from 'react-sortable-hoc';
import DragHandle from './DragHandle';
import { FieldArray, Formik, Form as FormikForm, Field } from 'formik';
import PropTypes from 'proptypes';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
import { |
b681b265 |
filter, find, flatten, identity, map, prop, propEq, reject, uniq, update |
22f488a7 |
} from 'rambda'; |
9d471776 |
import { insert, remove } from 'ramda';
import theme from '../theme'; |
22f488a7 |
const nextWorkerName = (rows) =>
`${rows.length + 1}. pracovník`;
class Form extends React.PureComponent {
state = { |
b681b265 |
allJobs: [], |
22f488a7 |
rows: [ |
9d471776 |
// {
// id: uuid(),
// name: 'pracovnik 1',
// jobs: [ |
b681b265 |
// { id: uuid(), name: 'frontend', t: '', d: '', w: '', anc: [] },
// { id: uuid(), name: 'backend', t: '', d: '', w: '', anc: [] } |
9d471776 |
// ]
// },
// {
// id: uuid(),
// name: 'pracovnik 2',
// jobs: [ |
b681b265 |
// { id: uuid(), name: 'frontend', t: '', d: '', w: '', anc: [] },
// { id: uuid(), name: 'backend', t: '', d: '', w: '', anc: [] } |
9d471776 |
// ]
// } |
b681b265 |
],
preempt: false, |
8c7841d1 |
flowShop: '0' |
22f488a7 |
};
|
9d471776 |
onSubmit = (values) => { |
b681b265 |
if (!values || !values.rows.length) return;
const { onSubmit } = this.props;
onSubmit(values); |
9d471776 |
};
|
b681b265 |
addWorker = (setFieldValue, rows, allJobs) => { |
9d471776 |
const jobs = [{ |
b681b265 |
id: uuid(), name: allJobs[0], t: '', d: '', w: '', anc: [] |
9d471776 |
}];
const updatedRows = [...rows, { id: uuid(), name: nextWorkerName(rows), jobs } ]; |
22f488a7 |
setFieldValue('rows', updatedRows);
};
|
b681b265 |
addTemplateWorker = (setFieldValue, allJobs) => (e) => {
if (e.keyCode !== undefined && e.keyCode !== 13) return;
e.preventDefault();
const val = this.new.value.replace(/:/g, '');
const updatedJobs = [...allJobs, val ];
setFieldValue('allJobs', updatedJobs);
this.new.value = null;
};
|
22f488a7 |
render() {
return ( |
8c7841d1 |
<Formik initialValues={this.state} onSubmit={this.onSubmit} render={({ setFieldValue, values: { rows, allJobs, flowShop } }) => ( |
9d471776 |
<FormikForm> |
8c7841d1 |
<h1>1/3 Vytvorenie úloh</h1> |
b681b265 |
<Box flexWrap="wrap" flexDirection="row">
{allJobs.map((_, key) => (
<Box key={key} width={300} flexDirection="row" marginBottom={10}>
<Box as={Field} name={`allJobs.${key}`} flex={1} marginRight={5} onChange={({ target: { value } }) => setFieldValue(`allJobs.${key}`, value)} />
<Box cursor="pointer" onClick={() => setFieldValue(`allJobs`, remove(key, 1, allJobs))} color="red" marginRight={20}>✕</Box>
</Box>
))} |
22f488a7 |
</Box> |
9d471776 |
|
b681b265 |
<Box width={300} flexDirection="row" marginBottom={10}> |
8c7841d1 |
<Box as="input" placeholder="Nová úloha" onKeyDown={this.addTemplateWorker(setFieldValue, allJobs)} nativeRef={c => this.new = c} marginRight={5} flex={1} /> |
b681b265 |
<Box cursor="pointer" onClick={this.addTemplateWorker(setFieldValue, allJobs)} color="green" fontSize="1rem">+</Box> |
9d471776 |
</Box> |
b681b265 |
{allJobs.length ? (
<div>
<h1>2/3 Konfigurácia projektu</h1>
{rows.length ?
<Box flexDirection="row" marginBottom={10}>
<Box flexBasis="20%" flexShrink={0}><strong>Pracovníci</strong></Box> |
8c7841d1 |
<Box flexBasis="63%" flexShrink={0}><strong>Priradené úlohy</strong></Box> |
b681b265 |
</Box> : null
}
<FieldArray name="rows" render={arrayHelpers => (
<React.Fragment>
{rows.map((row, i) => <Row key={i} {...row} arrayHelpers={arrayHelpers}
prefix={`rows.${i}`} index={i}/>)}
</React.Fragment>
)}/>
<Box flexDirection="row" marginBottom={10}>
<Box
flexShrink={0}
onClick={() => this.addWorker(setFieldValue, rows, allJobs)}
{...theme.buttons.primary}
>
+ Nový pracovník
</Box>
</Box>
{rows.length ?
<div> |
8c7841d1 |
<Box flexDirection="row" justifyContent="center" marginBottom={10}>
<Box as="label" justifyContent="center" flexDirection="row" marginRight={5}>
<Field type="radio" name="flowShop" value="1" checked={flowShop == '1'} disabled={rows.length === 1} />
<Box as="em" color="gray" textAlign="center" fontSize="0.9em">
Flow shop
</Box>
</Box>
<Box as="label" justifyContent="center" flexDirection="row">
<Field type="radio" name="flowShop" value="0" checked={flowShop == '0'} disabled={rows.length === 1} />
<Box as="em" color="gray" textAlign="center" fontSize="0.9em">
Pracovníci zvládnu každú úlohu
</Box>
</Box>
</Box>
|
b681b265 |
<Box as="label" justifyContent="center" flexDirection="row"> |
8c7841d1 |
<Field type="checkbox" name="preempt" disabled={flowShop == '1' || rows.length === 1}/> |
b681b265 |
<Box as="em" color="gray" textAlign="center" fontSize="0.9em">
Úlohy pracovníkov možno prerušiť
</Box>
</Box>
<Box as="button" margin="auto" marginTop={10} width="20rem" alignItems="center"
padding={5} {...theme.buttons.primary} backgroundColor="#C89C93" fontSize="1rem">
Vytvoriť rozvrh
</Box>
</div>
: null}
</div>
) : null} |
9d471776 |
</FormikForm> |
22f488a7 |
)}>
</Formik>
)
}
}
const SortableJob = SortableElement(
class extends React.PureComponent {
static contextTypes = {
formik: PropTypes.object
};
handleSelectChange = (type, selectedPairs) => {
const { prefix } = this.props;
const { formik: { setFieldValue } } = this.context;
const selected = map(prop('value'), selectedPairs);
setFieldValue(`${prefix}.${type}`, selected);
};
deleteJob = () => {
const { rowIndex, myIndex } = this.props;
const { formik: { setFieldValue, values: { rows } } } = this.context;
const jobs = filter(identity, update(myIndex, null, rows[rowIndex].jobs));
if (jobs.length) {
setFieldValue(`rows.${rowIndex}.jobs`, jobs);
} else {
const updatedRows = filter(identity, update(rowIndex, null, rows));
setFieldValue(`rows`, updatedRows);
}
};
render() { |
b681b265 |
const { prefix, job, existingJobs: _existingJobs } = this.props; |
8c7841d1 |
const { formik: { values: { rows, allJobs, flowShop } } } = this.context; |
22f488a7 |
const existingJobs = map(
value => {
const [idRow, idJob] = value.split(':');
const row = find(propEq('id', idRow), rows);
const jobName = find(propEq('id', idJob), row.jobs).name;
|
b681b265 |
return ({ value, label: `${row.name} – ${jobName}` }); |
22f488a7 |
},
reject(
value => {
const [, idJob] = value.split(':');
return idJob === job.id;
},
_existingJobs
)
);
return ( |
9d471776 |
<Box
flexDirection="row"
marginBottom={10}
paddingBottom={10}
className="sortable-inactive"
> |
8c7841d1 |
{flowShop == '1' ?
<Box marginRight={10} fontSize="1.5rem" width={20}>
<DragHandle/>
</Box>
: null} |
22f488a7 |
|
9d471776 |
<Box flex={1} padding={1}> |
22f488a7 |
<Box width={200} marginBottom={10} flexDirection="row"> |
b681b265 |
<Field component="select" name={`${prefix}.name`}>
{allJobs.map((job, key) => (
<option value={job} key={key}>{job}</option> |
22f488a7 |
))}
</Field> |
9d471776 |
<Box cursor="pointer" onClick={this.deleteJob} marginLeft={5} color="red">✕</Box> |
22f488a7 |
</Box>
<Box flexDirection="row" justifyContent="space-between" marginBottom={10}>
<Box flexBasis="30%">
<label>
Trvanie: <br /> |
b681b265 |
<Field type="number" min={0} step={1} name={`${prefix}.t`} style={{ width: '100%' }} required /> |
22f488a7 |
</label>
</Box>
<Box flexBasis="30%">
<label>
Deadline: <br /> |
b681b265 |
<Field type="number" min={0} step={1} name={`${prefix}.d`} style={{ width: '100%' }} disabled={rows.length > 1} /> |
22f488a7 |
</label>
</Box>
<Box flexBasis="30%">
<label> |
b681b265 |
Váha: (1-10) <br />
<Field type="number" min={1} max={10} step={1} name={`${prefix}.w`} style={{ width: '100%' }} /> |
22f488a7 |
</label>
</Box>
</Box>
<Box> |
b681b265 |
{/*<Box as="label" flexDirection="row" marginBottom={5}>*/}
{/*<Field type="radio" name={`${prefix}.succOrAnc`} value={0} checked={job.succOrAnc == '0'} disabled={rows[rowIndex].linkedTo}/> Bez nadväznosti*/}
{/*</Box>*/} |
22f488a7 |
<Box marginBottom={5}>
<Box as="label" flexDirection="row"> |
b681b265 |
Predchodcovia |
22f488a7 |
</Box> |
b681b265 |
<Box flex={1} marginTop={5}> |
22f488a7 |
<Select
multi
onChange={e => this.handleSelectChange('anc', e)}
options={existingJobs}
removeSelected
value={job.anc}
/>
</Box>
</Box>
</Box>
</Box>
</Box>
);
}
});
|
9d471776 |
const SortableList = SortableContainer(({ jobs, existingJobs, prefix, rowIndex, draggingIndex }) => ( |
22f488a7 |
<div>
{jobs.map((job, i) => (
<SortableJob
key={job.id}
index={i}
myIndex={i}
rowIndex={rowIndex}
job={job}
prefix={`${prefix}.${i}`}
existingJobs={existingJobs} |
9d471776 |
isDragging={draggingIndex === i} |
22f488a7 |
/>
))}
</div>
));
class Row extends React.PureComponent {
static contextTypes = {
formik: PropTypes.object
};
|
9d471776 |
state = {
draggingIndex: -1
};
onSortStart = ({ index }) => {
this.setState({ draggingIndex: index });
};
|
22f488a7 |
onSortEnd = ({ oldIndex, newIndex }) => {
const { prefix, index } = this.props;
const { formik: { setFieldValue, values: { rows }} } = this.context;
const updatedJobs = arrayMove(rows[index].jobs, oldIndex, newIndex);
setFieldValue(`${prefix}.jobs`, updatedJobs); |
9d471776 |
this.setState({ draggingIndex: -1 }); |
22f488a7 |
};
findExistingJobs = () => {
const { formik: { values: { rows } } } = this.context;
const existingJobs = uniq(
flatten(
map(({ id: idRow, jobs }) =>
map(({ id: idJob }) => `${idRow}:${idJob}`, jobs),
rows)
)
);
return existingJobs;
};
addJob = () => {
const { index } = this.props; |
b681b265 |
const { formik: { setFieldValue, values: { rows, allJobs } } } = this.context; |
22f488a7 |
const newJob = { |
b681b265 |
id: uuid(), name: allJobs[0], t: '', d: '', w: '', anc: [] |
22f488a7 |
};
const jobs = [...rows[index].jobs, newJob];
setFieldValue(`rows.${index}.jobs`, jobs);
};
|
9d471776 |
deleteRow = () => { |
b681b265 |
const { index } = this.props; |
9d471776 |
const { formik: { setFieldValue, values: { rows } } } = this.context;
let newRows = remove(index, 1, rows);
setFieldValue('rows', newRows);
};
|
22f488a7 |
duplicate = () => { |
b681b265 |
const { index, name, preempt, jobs } = this.props; |
22f488a7 |
const { formik: { setFieldValue, values: { rows } } } = this.context;
const jobsCopy = map(job => ({ ...job }), jobs); |
b681b265 |
const newRow = { id: uuid(), name: `${name} – kópia`, preempt, jobs: jobsCopy }; |
22f488a7 |
const updatedRows = insert(index + 1, newRow, rows);
setFieldValue('rows', updatedRows);
};
render() { |
b681b265 |
const { index, prefix, name, jobs } = this.props; |
9d471776 |
const { draggingIndex } = this.state;
|
22f488a7 |
const existingJobs = this.findExistingJobs();
return ( |
9d471776 |
<Box flexDirection="row" marginBottom={30} padding={10} className="row"> |
b681b265 |
<Box flexBasis="20%" flexShrink={0} paddingRight={10} flexDirection="row"> |
9d471776 |
<strong>{name}</strong> |
b681b265 |
<Box cursor="pointer" onClick={this.deleteRow} color="red" marginLeft={5}>✕</Box> |
9d471776 |
</Box>
<Box flexBasis="63%" flexShrink={0}> |
22f488a7 |
<FieldArray render={() => ( |
9d471776 |
<SortableList
helperClass="sorting"
onSortStart={this.onSortStart}
onSortEnd={this.onSortEnd}
jobs={jobs}
useDragHandle={true}
existingJobs={existingJobs}
prefix={`${prefix}.jobs`}
rowIndex={index}
draggingIndex={draggingIndex}
/> |
22f488a7 |
)} /> |
9d471776 |
<Box onClick={this.addJob} {...theme.buttons.secondary} alignSelf="flex-start">
+ Nová úloha
</Box>
</Box> |
b681b265 |
<Box alignItems="center" flex={1}> |
9d471776 |
<Box
onClick={this.duplicate}
{...theme.buttons.tertiary}
flexDirection="row"
alignItems="center"
flexWrap="wrap"
justifyContent="center"
paddingVertical={1}
>
<span style={{ fontSize: '1.8em', paddingRight: '0.2em' }}>⩇</span> Duplikovať
</Box> |
22f488a7 |
</Box>
</Box>
)
}
}
export default Form; |