src/components/Form.js
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 {
   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;