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 {
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;