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 { filter, find, flatten, identity, map, prop, propEq, reject, uniq, update } from 'rambda'; import { insert } from 'ramda'; const allJobs = { frontend: 'Frontend', backend: 'Backend', }; const nextWorkerName = (rows) => `${rows.length + 1}. pracovník`; class Form extends React.PureComponent { state = { rows: [ { id: uuid(), name: 'pracovnik 1', preempt: false, linkedTo: null, jobs: [ { id: uuid(), name: 'frontend', t: '', d: '', w: '', succ: [], anc: [], succOrAnc: 0 }, { id: uuid(), name: 'backend', t: '', d: '', w: '', succ: [], anc: [], succOrAnc: 0 } ] }, { id: uuid(), name: 'pracovnik 2', preempt: false, linkedTo: null, jobs: [ { id: uuid(), name: 'frontend', t: '', d: '', w: '', succ: [], anc: [], succOrAnc: 0 }, { id: uuid(), name: 'backend', t: '', d: '', w: '', succ: [], anc: [], succOrAnc: 0 } ] } ] }; addWorker = (setFieldValue, rows) => { const updatedRows = [...rows, { id: uuid(), name: nextWorkerName(rows), jobs: [] } ]; setFieldValue('rows', updatedRows); }; render() { return ( <Formik initialValues={this.state} render={({ setFieldValue, values: { rows } }) => ( <React.Fragment> <h1>Konfigurácia projektu</h1> <Box flexDirection="row" marginBottom={10}> <Box flexBasis="20%" flexShrink={0}><strong>Pracovník</strong></Box> <Box flexBasis="60%" flexShrink={0}><strong>Úlohy</strong></Box> <Box flexBasis="10%" flexShrink={0} align="center"><strong>Prerušenie</strong></Box> </Box> <FieldArray name="rows" render={arrayHelpers => ( <FormikForm> {rows.map((row, i) => <Row key={i} {...row} arrayHelpers={arrayHelpers} prefix={`rows.${i}`} index={i} />)} </FormikForm> )} /> <Box flexDirection="row" marginBottom={10}> <Box flexBasis="20%" flexShrink={0} onClick={() => this.addWorker(setFieldValue, rows)} style={{ cursor: 'pointer' }}> + Nový pracovník </Box> </Box> </React.Fragment> )}> </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() { const { myIndex, rowIndex, prefix, job, existingJobs: _existingJobs } = this.props; const { formik: { values: { rows } } } = this.context; 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; return ({ value, label: `${row.name} – ${jobName}` }); }, reject( value => { const [, idJob] = value.split(':'); return idJob === job.id; }, _existingJobs ) ); return ( <Box flexDirection="row" marginBottom={10} paddingBottom={10} backgroundColor="white" borderBottomColor="#1B1B3A" borderBottomWidth={1} borderStyle="solid"> <Box marginRight={10} style={{ fontSize: '1.5rem' }}> <DragHandle /> </Box> <Box flex={1}> <Box width={200} marginBottom={10} flexDirection="row"> <Field component="select" name={`${prefix}.name`}> {Object.keys(allJobs).map(key => ( <option value={key} key={key}>{allJobs[key]}</option> ))} </Field> <Box style={{ cursor: 'pointer' }} onClick={this.deleteJob} marginLeft={5}>✕</Box> </Box> <Box flexDirection="row" justifyContent="space-between" marginBottom={10}> <Box flexBasis="30%"> <label> Trvanie: <br /> <Field type="number" step={1} name={`${prefix}.t`} style={{ width: '100%' }} /> </label> </Box> <Box flexBasis="30%"> <label> Deadline: <br /> <Field type="number" step={1} name={`${prefix}.d`} style={{ width: '100%' }} /> </label> </Box> <Box flexBasis="30%"> <label> Váha: <br /> <Field type="number" min={0} max={1} step={0.1} name={`${prefix}.w`} style={{ width: '100%' }} /> </label> </Box> </Box> <Box> <Box as="label" flexDirection="row" marginBottom={5}> <Field type="radio" name={`${prefix}.succOrAnc`} value={0} /> Bez nadväznosti </Box> <Box marginBottom={5}> <Box as="label" flexDirection="row"> <Field type="radio" name={`${prefix}.succOrAnc`} value={1}/> Predchodcovia </Box> <Box flex={1} marginLeft={10} marginTop={5}> <Select disabled={false} multi onChange={e => this.handleSelectChange('anc', e)} options={existingJobs} removeSelected value={job.anc} /> </Box> </Box> <Box> <Box as="label" flexDirection="row"> <Field type="radio" name={`${prefix}.succOrAnc`} value={2} /> Nasledovníci </Box> <Box flex={1} marginLeft={10} marginTop={5}> <Select disabled={false} multi onChange={e => this.handleSelectChange('succ', e)} options={existingJobs} removeSelected value={job.succ} /> </Box> </Box> </Box> </Box> </Box> ); } }); const SortableList = SortableContainer(({ jobs, existingJobs, prefix, rowIndex }) => ( <div> {jobs.map((job, i) => ( <SortableJob key={job.id} index={i} myIndex={i} rowIndex={rowIndex} job={job} prefix={`${prefix}.${i}`} existingJobs={existingJobs} /> ))} </div> )); class Row extends React.PureComponent { static contextTypes = { formik: PropTypes.object }; 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); }; 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; const { formik: { setFieldValue, values: { rows } } } = this.context; const newJob = { id: uuid(), name: 'frontend', t: '', d: '', w: '', succ: [], anc: [], succOrAnc: 0 }; const jobs = [...rows[index].jobs, newJob]; setFieldValue(`rows.${index}.jobs`, jobs); }; duplicate = () => { const { id, index, name, preempt, jobs } = this.props; const { formik: { setFieldValue, values: { rows } } } = this.context; const jobsCopy = map(job => ({ ...job }), jobs); const newRow = { id: uuid(), name: `${name} - kópia`, preempt, linkedTo: id, jobs: jobsCopy }; const updatedRows = insert(index + 1, newRow, rows); setFieldValue('rows', updatedRows); }; render() { const { index, prefix, name, preempt, linkedTo, jobs } = this.props; const existingJobs = this.findExistingJobs(); return ( <Box flexDirection="row" marginBottom={30}> <Box flexBasis="20%" flexShrink={0}>{name}</Box> <Box flexBasis="60%" flexShrink={0}> <FieldArray render={() => ( <SortableList jobs={jobs} onSortStart={this.onSortStart} onSortEnd={this.onSortEnd} useDragHandle={true} existingJobs={existingJobs} prefix={`${prefix}.jobs`} rowIndex={index} /> )} /> <Box onClick={this.addJob} style={{ cursor: 'pointer' }}>+ Nová úloha</Box> </Box> <Box flexBasis="10%" flexShrink={0} alignItems="center"> <Field type="checkbox" name={`${prefix}.preempt`} /> </Box> <Box alignItems="flex-end"><button type="button" onClick={this.duplicate}>Duplikovať a spojiť pracovníka</button></Box> </Box> ) } } export default Form;