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, remove } from 'ramda'; import theme from '../theme'; const nextWorkerName = (rows) => `${rows.length + 1}. pracovník`; class Form extends React.PureComponent { state = { allJobs: [], rows: [ // { // id: uuid(), // name: 'pracovnik 1', // jobs: [ // { id: uuid(), name: 'frontend', t: '', d: '', w: '', anc: [] }, // { id: uuid(), name: 'backend', t: '', d: '', w: '', anc: [] } // ] // }, // { // id: uuid(), // name: 'pracovnik 2', // jobs: [ // { id: uuid(), name: 'frontend', t: '', d: '', w: '', anc: [] }, // { id: uuid(), name: 'backend', t: '', d: '', w: '', anc: [] } // ] // } ], preempt: false, flowShop: '0' }; onSubmit = (values) => { if (!values || !values.rows.length) return; const { onSubmit } = this.props; onSubmit(values); }; addWorker = (setFieldValue, rows, allJobs) => { const jobs = [{ id: uuid(), name: allJobs[0], t: '', d: '', w: 1, anc: [] }]; const updatedRows = [...rows, { id: uuid(), name: nextWorkerName(rows), jobs } ]; setFieldValue('rows', updatedRows); }; 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; }; render() { return ( <Formik initialValues={this.state} onSubmit={this.onSubmit} render={({ setFieldValue, values: { rows, allJobs, flowShop } }) => ( <FormikForm> <h1>1/3 Vytvorenie úloh</h1> <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> ))} </Box> <Box width={300} flexDirection="row" marginBottom={10}> <Box as="input" placeholder="Nová úloha" onKeyDown={this.addTemplateWorker(setFieldValue, allJobs)} nativeRef={c => this.new = c} marginRight={5} flex={1} /> <Box cursor="pointer" onClick={this.addTemplateWorker(setFieldValue, allJobs)} color="green" fontSize="1rem">+</Box> </Box> {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> <Box flexBasis="63%" flexShrink={0}><strong>Priradené úlohy</strong></Box> </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> <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> <Box as="label" justifyContent="center" flexDirection="row"> <Field type="checkbox" name="preempt" disabled={flowShop == '1' || rows.length === 1}/> <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} </FormikForm> )}> </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 { prefix, job, existingJobs: _existingJobs } = this.props; const { formik: { values: { rows, allJobs, flowShop } } } = 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} className="sortable-inactive" > {flowShop == '1' ? <Box marginRight={10} fontSize="1.5rem" width={20}> <DragHandle/> </Box> : null} <Box flex={1} padding={1}> <Box width={200} marginBottom={10} flexDirection="row"> <Field component="select" name={`${prefix}.name`}> {allJobs.map((job, key) => ( <option value={job} key={key}>{job}</option> ))} </Field> <Box cursor="pointer" onClick={this.deleteJob} marginLeft={5} color="red">✕</Box> </Box> <Box flexDirection="row" justifyContent="space-between" marginBottom={10}> <Box flexBasis="30%"> <label> Trvanie: <br /> <Field type="number" min={0} step={1} name={`${prefix}.t`} style={{ width: '100%' }} required /> </label> </Box> <Box flexBasis="30%"> <label> Deadline: <br /> <Field type="number" min={0} step={1} name={`${prefix}.d`} style={{ width: '100%' }} disabled={rows.length > 1} /> </label> </Box> <Box flexBasis="30%"> <label> Váha: <br /> <Field type="range" min={1} max={10} step={1} name={`${prefix}.w`} style={{ width: '100%' }} list="tickmarks" /> <Box flexDirection="row" justifyContent="space-between"> <Box marginLeft={5}>1</Box> <Box marginLeft={-15}>5</Box> <div>10</div> </Box> <datalist id="tickmarks"> <option value="1" /> <option value="2" /> <option value="3" /> <option value="4" /> <option value="5" /> <option value="6" /> <option value="7" /> <option value="8" /> <option value="9" /> <option value="10" /> </datalist> </label> </Box> </Box> <Box> {/*<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>*/} <Box marginBottom={5}> <Box as="label" flexDirection="row"> Predchodcovia </Box> <Box flex={1} marginTop={5}> <Select multi onChange={e => this.handleSelectChange('anc', e)} options={existingJobs} removeSelected value={job.anc} /> </Box> </Box> </Box> </Box> </Box> ); } }); const SortableList = SortableContainer(({ jobs, existingJobs, prefix, rowIndex, draggingIndex }) => ( <div> {jobs.map((job, i) => ( <SortableJob key={job.id} index={i} myIndex={i} rowIndex={rowIndex} job={job} prefix={`${prefix}.${i}`} existingJobs={existingJobs} isDragging={draggingIndex === i} /> ))} </div> )); class Row extends React.PureComponent { static contextTypes = { formik: PropTypes.object }; state = { draggingIndex: -1 }; onSortStart = ({ index }) => { this.setState({ draggingIndex: index }); }; 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); this.setState({ draggingIndex: -1 }); }; 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, allJobs } } } = this.context; const newJob = { id: uuid(), name: allJobs[0], t: '', d: '', w: 1, anc: [] }; const jobs = [...rows[index].jobs, newJob]; setFieldValue(`rows.${index}.jobs`, jobs); }; deleteRow = () => { const { index } = this.props; const { formik: { setFieldValue, values: { rows } } } = this.context; let newRows = remove(index, 1, rows); setFieldValue('rows', newRows); }; duplicate = () => { const { 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, jobs: jobsCopy }; const updatedRows = insert(index + 1, newRow, rows); setFieldValue('rows', updatedRows); }; render() { const { index, prefix, name, jobs } = this.props; const { draggingIndex } = this.state; const existingJobs = this.findExistingJobs(); return ( <Box flexDirection="row" marginBottom={30} padding={10} className="row"> <Box flexBasis="20%" flexShrink={0} paddingRight={10} flexDirection="row"> <strong>{name}</strong> <Box cursor="pointer" onClick={this.deleteRow} color="red" marginLeft={5}>✕</Box> </Box> <Box flexBasis="63%" flexShrink={0}> <FieldArray render={() => ( <SortableList helperClass="sorting" onSortStart={this.onSortStart} onSortEnd={this.onSortEnd} jobs={jobs} useDragHandle={true} existingJobs={existingJobs} prefix={`${prefix}.jobs`} rowIndex={index} draggingIndex={draggingIndex} /> )} /> <Box onClick={this.addJob} {...theme.buttons.secondary} alignSelf="flex-start"> + Nová úloha </Box> </Box> <Box alignItems="center" flex={1}> <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> </Box> </Box> ) } } export default Form;