Commit e4404877 authored by hh1966's avatar hh1966
Browse files

Add table field to research plan detail

parent 1400785b
Pipeline #51440 failed with stage
......@@ -7,7 +7,7 @@ import DeviceDetails from './DeviceDetails';
import ReactionDetails from './ReactionDetails';
import WellplateDetails from './WellplateDetails';
import ScreenDetails from './ScreenDetails';
import ResearchPlanDetails from './ResearchPlanDetails';
import ResearchPlanDetails from './research_plan/ResearchPlanDetails';
import { ConfirmModal } from './common/ConfirmModal';
import ReportContainer from './report/ReportContainer';
import FormatContainer from './FormatContainer';
......
......@@ -45,6 +45,28 @@ export default class ResearchPlan extends Element {
})
break;
case 'table':
const columns = ['a', 'b'].map(columnName => {
return {
key: columnName,
name: columnName,
editable: true,
resizable: true,
width: 200
}
})
this.body.push({
id: uuidv4(),
type: 'table',
value: {
columns: columns,
rows: [
{a: '1', b: ''},
{a: '2', b: ''},
{a: '3', b: ''}
]
}
})
break;
case 'image':
this.body.push({
......@@ -55,6 +77,7 @@ export default class ResearchPlan extends Element {
public_name: null,
}
})
break;
}
}
......
......@@ -3,23 +3,22 @@ import PropTypes from 'prop-types';
import { Panel, ListGroup, ListGroupItem, ButtonToolbar, Button, Tooltip, OverlayTrigger, Row, Col, Tabs, Tab } from 'react-bootstrap';
import SVG from 'react-inlinesvg';
import { includes, last, findKey, values } from 'lodash';
import ElementCollectionLabels from './ElementCollectionLabels';
import UIStore from './stores/UIStore';
import UIActions from './actions/UIActions';
import ElementCollectionLabels from '../ElementCollectionLabels';
import UIStore from '../stores/UIStore';
import UIActions from '../actions/UIActions';
import ElementActions from './actions/ElementActions';
import DetailActions from './actions/DetailActions';
import ResearchPlansFetcher from './fetchers/ResearchPlansFetcher';
import ResearchPlansLiteratures from './DetailsTabLiteratures';
import Attachment from './models/Attachment';
import Utils from './utils/Functions';
import ElementActions from '../actions/ElementActions';
import DetailActions from '../actions/DetailActions';
import ResearchPlansFetcher from '../fetchers/ResearchPlansFetcher';
import ResearchPlansLiteratures from '../DetailsTabLiteratures';
import Attachment from '../models/Attachment';
import Utils from '../utils/Functions';
import LoadingActions from '../actions/LoadingActions';
import LoadingActions from './actions/LoadingActions';
import ResearchPlanDetailsAttachments from './research_plan/ResearchPlanDetailsAttachments';
import ResearchPlanDetailsBody from './research_plan/ResearchPlanDetailsBody';
import ResearchPlanDetailsNameField from './research_plan/ResearchPlanDetailsNameField';
import ResearchPlanDetailsStatic from './research_plan/ResearchPlanDetailsStatic';
import ResearchPlanDetailsAttachments from './ResearchPlanDetailsAttachments';
import ResearchPlanDetailsBody from './ResearchPlanDetailsBody';
import ResearchPlanDetailsName from './ResearchPlanDetailsName';
import ResearchPlanDetailsStatic from './ResearchPlanDetailsStatic';
export default class ResearchPlanDetails extends Component {
......@@ -192,7 +191,7 @@ export default class ResearchPlanDetails extends Component {
return (
<ListGroup fill="true">
<ListGroupItem>
<ResearchPlanDetailsNameField value={name}
<ResearchPlanDetailsName value={name}
disabled={research_plan.isMethodDisabled('name')}
onChange={this.handleNameChange.bind(this)} />
......
......@@ -4,9 +4,11 @@ import { FormGroup, Button, Row, Col, ControlLabel } from 'react-bootstrap'
import ResearchPlanDetailsDragSource from './ResearchPlanDetailsDragSource'
import ResearchPlanDetailsDropTarget from './ResearchPlanDetailsDropTarget'
import ResearchPlanDetailsRichTextField from './ResearchPlanDetailsRichTextField'
import ResearchPlanDetailsKetcherField from './ResearchPlanDetailsKetcherField'
import ResearchPlanDetailsImageField from './ResearchPlanDetailsImageField'
import ResearchPlanDetailsFieldRichText from './ResearchPlanDetailsFieldRichText'
import ResearchPlanDetailsFieldKetcher from './ResearchPlanDetailsFieldKetcher'
import ResearchPlanDetailsFieldImage from './ResearchPlanDetailsFieldImage'
import ResearchPlanDetailsFieldTable from './ResearchPlanDetailsFieldTable'
export default class ResearchPlanDetailsField extends Component {
......@@ -17,21 +19,28 @@ export default class ResearchPlanDetailsField extends Component {
switch (field.type) {
case 'richtext':
label = 'Text'
component = <ResearchPlanDetailsRichTextField key={field.id}
component = <ResearchPlanDetailsFieldRichText key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
break;
case 'ketcher':
label = 'Schema'
component = <ResearchPlanDetailsKetcherField key={field.id}
component = <ResearchPlanDetailsFieldKetcher key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
break;
case 'image':
label = 'Image'
component = <ResearchPlanDetailsImageField key={field.id}
component = <ResearchPlanDetailsFieldImage key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
break;
case 'table':
label = 'Table'
component = <ResearchPlanDetailsFieldTable key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
break;
}
return (
......@@ -41,14 +50,13 @@ export default class ResearchPlanDetailsField extends Component {
</Col>
<Col md={12}>
<div className="field">
<Button bsStyle="danger" bsSize="xsmall" onClick={() => onDelete(field.id)} >
<i className="fa fa-times"></i>
<Button className="pull-right" bsStyle="danger" bsSize="xsmall"
onClick={() => onDelete(field.id)} >
<i className="fa fa-times"></i>
</Button>
<ResearchPlanDetailsDragSource index={index} onDrop={onDrop.bind(this)}/>
<ControlLabel>{label}</ControlLabel>
<FormGroup>
{component}
</FormGroup>
{component}
</div>
</Col>
</Row>
......
......@@ -5,7 +5,7 @@ import Dropzone from 'react-dropzone'
import ResearchPlansFetcher from '../fetchers/ResearchPlansFetcher'
export default class ResearchPlanDetailsImageField extends Component {
export default class ResearchPlanDetailsFieldImage extends Component {
handleDrop(files) {
let { field, onChange } = this.props
......@@ -48,7 +48,7 @@ export default class ResearchPlanDetailsImageField extends Component {
}
ResearchPlanDetailsImageField.propTypes = {
ResearchPlanDetailsFieldImage.propTypes = {
field: PropTypes.object,
index: PropTypes.number,
disabled: PropTypes.bool,
......
......@@ -8,7 +8,7 @@ import ResearchPlansFetcher from '../fetchers/ResearchPlansFetcher'
import StructureEditorModal from '../structure_editor/StructureEditorModal'
import QuillEditor from '../QuillEditor'
export default class ResearchPlanDetailsKetcherField extends Component {
export default class ResearchPlanDetailsFieldKetcher extends Component {
constructor(props) {
super(props)
......@@ -103,7 +103,7 @@ export default class ResearchPlanDetailsKetcherField extends Component {
}
}
ResearchPlanDetailsKetcherField.propTypes = {
ResearchPlanDetailsFieldKetcher.propTypes = {
field: PropTypes.object,
index: PropTypes.number,
disabled: PropTypes.bool,
......
......@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import QuillEditor from '../QuillEditor'
export default class ResearchPlanDetailsRichTextField extends Component {
export default class ResearchPlanDetailsFieldRichText extends Component {
render() {
let { field, index, disabled, onChange } = this.props
......@@ -18,7 +18,7 @@ export default class ResearchPlanDetailsRichTextField extends Component {
}
ResearchPlanDetailsRichTextField.propTypes = {
ResearchPlanDetailsFieldRichText.propTypes = {
field: PropTypes.object,
index: PropTypes.number,
disabled: PropTypes.bool,
......
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { range } from 'lodash';
import ReactDataGrid from 'react-data-grid';
import { Menu } from "react-data-grid-addons"
import ResearchPlanDetailsFieldTableContextMenu from './ResearchPlanDetailsFieldTableContextMenu'
import ResearchPlanDetailsFieldTableToolbar from './ResearchPlanDetailsFieldTableToolbar'
import ResearchPlanDetailsFieldTableColumnNameModal from './ResearchPlanDetailsFieldTableColumnNameModal'
const { ContextMenuTrigger } = Menu
const defaultParsePaste = str => (
str.split(/\r\n|\n|\r/).map(row => row.split('\t'))
)
export default class ResearchPlanDetailsFieldTable extends Component {
constructor(props) {
super(props);
this.state = {
showModal: '',
idx: null
}
// document.addEventListener('copy', this.handleCopy.bind(this))
document.addEventListener('paste', this.handlePaste.bind(this))
this.handleCellSelected = this.handleCellSelected.bind(this)
this.handleCellDeSelected = this.handleCellDeSelected.bind(this)
}
buildColumn(columnName) {
return {
key: columnName,
name: columnName,
editable: true,
resizable: true,
width: 200
}
}
buildRow() {
return []
}
handleEdit(event) {
const { fromRow, toRow, updated } = event
const { field, onChange } = this.props
for (let i = fromRow; i <= toRow; i++) {
field.value.rows[i] = { ...field.value.rows[i], ...updated }
}
onChange(field.value, field.id)
}
handleCellSelected(event) {
this.setState({ selected: event })
}
handleCellDeSelected(event) {
this.setState({ selected: {} })
}
handleColumnNameModalOpen(showModal, idx) {
this.setState({
showModal: showModal,
idx: idx
})
}
handleColumnNameModalSubmit(columnName) {
const { showModal, idx } = this.state
if (columnName) {
if (showModal == 'insert') {
this.handleColumnInsert(idx, columnName)
} else if (showModal == 'rename') {
this.handleColumnRename(idx, columnName)
}
}
this.setState({
showModal: '',
idx: null
})
}
handleColumnInsert(columnIdx, columnName) {
const { field, onChange } = this.props
const columns = field.value.columns.slice()
columns.splice(columnIdx, 0, this.buildColumn(columnName));
field.value.columns = columns
onChange(field.value, field.id)
}
handleColumnRename(columnIdx, columnName) {
const { field, onChange } = this.props
const columns = field.value.columns.slice()
const rows = field.value.rows.slice()
const oldColumnName = columns[columnIdx]['key']
const column = Object.assign({}, columns[columnIdx], {
key: columnName,
name: columnName
})
columns.splice(columnIdx, 1, column);
field.value.columns = columns
for (let i = 0; i < rows.length; i++) {
rows[i][columnName] = rows[i][oldColumnName]
delete rows[i][oldColumnName]
}
onChange({ columns, rows }, field.id)
}
handleColumnResize(columnIdx, width) {
const { field, onChange } = this.props
field.value.columns[columnIdx]['width'] = width
onChange(field.value, field.id)
}
handleColumnDelete(columnIdx) {
const { field, onChange } = this.props
const columns = field.value.columns.slice()
columns.splice(columnIdx, 1)
field.value.columns = columns
onChange(field.value, field.id)
}
handleRowInsert(rowIdx) {
const { field, onChange } = this.props
field.value.rows.splice(rowIdx, 0, this.buildRow());
onChange(field.value, field.id)
}
handleRowDelete(rowIdx) {
const { field, onChange } = this.props
field.value.rows.splice(rowIdx, 1)
onChange(field.value, field.id)
}
handlePaste(event) {
event.preventDefault();
const { field, onChange } = this.props
const { selected } = this.state;
const newRows = [];
const pasteData = defaultParsePaste(event.clipboardData.getData('text/plain'));
const colIdx = selected.idx
const rowIdx = selected.rowIdx
pasteData.forEach((row) => {
const rowData = {};
// Merge the values from pasting and the keys from the columns
field.value.columns.slice(colIdx, colIdx + row.length).forEach((col, j) => {
// Create the key-value pair for the row
rowData[col.key] = row[j];
});
// Push the new row to the changes
newRows.push(rowData);
});
for (let i = 0; i < newRows.length; i++) {
if (rowIdx + i < field.value.rows.length) {
field.value.rows[rowIdx + i] = { ...field.value.rows[rowIdx + i], ...newRows[i] };
}
}
onChange(field.value, field.id)
}
handleCopy(event) {
event.preventDefault();
const { columns } = this.props
const { selection } = this.state;
// Loop through each row
const text = range(selection.topLeft.rowIdx, selection.botRight.rowIdx + 1).map(
// Loop through each column
rowIdx => columns.slice(selection.topLeft.colIdx, selection.botRight.colIdx + 1).map(
// Grab the row values and make a text string
col => this.rowGetter(rowIdx)[col.key],
).join('\t'),
).join('\n');
event.clipboardData.setData('text/plain', text);
}
rowGetter(idx) {
return this.props.field.value.rows[idx]
}
render() {
const { field } = this.props
const { rows, columns } = field.value
const { showModal } = this.state
const editorPortalTarget = document.getElementsByClassName('react-grid-Viewport')[0]
return (
<div>
<ReactDataGrid
columns={columns}
rowGetter={this.rowGetter.bind(this)}
rowsCount={rows.length}
minHeight={272}
onGridRowsUpdated={event => this.handleEdit(event)}
enableCellSelect={true}
editorPortalTarget={editorPortalTarget}
onCellSelected={this.handleCellSelected.bind(this)}
onCellDeSelected={this.handleCellDeSelected.bind(this)}
onColumnResize={this.handleColumnResize.bind(this)}
toolbar={
<ResearchPlanDetailsFieldTableToolbar
/>
}
contextMenu={
<ResearchPlanDetailsFieldTableContextMenu
onColumnInsertLeft={(event, { idx }) => this.handleColumnNameModalOpen('insert', idx)}
onColumnInsertRight={(event, { idx }) => this.handleColumnNameModalOpen('insert', idx + 1)}
onColumnRename={(event, { idx }) => this.handleColumnNameModalOpen('rename', idx)}
onColumnDelete={(event, { idx }) => this.handleColumnDelete(idx)}
onRowInsertAbove={(event, { rowIdx }) => this.handleRowInsert(rowIdx)}
onRowInsertBelow={(event, { rowIdx }) => this.handleRowInsert(rowIdx + 1)}
onRowDelete={(event, { rowIdx }) => this.handleRowDelete(rowIdx)}
/>
}
RowsContainer={ContextMenuTrigger}
/>
<ResearchPlanDetailsFieldTableColumnNameModal
showModal={showModal}
columns={columns}
onSubmit={this.handleColumnNameModalSubmit.bind(this)}/>
</div>
)
}
}
ResearchPlanDetailsFieldTable.propTypes = {
field: PropTypes.object,
index: PropTypes.number,
disabled: PropTypes.bool,
onChange: PropTypes.func,
}
import React, { Component} from "react"
import PropTypes from 'prop-types'
import { Modal, ButtonToolbar, Button, FormGroup, ControlLabel, FormControl, HelpBlock } from 'react-bootstrap'
class ResearchPlanDetailsFieldTableColumnNameModal extends Component {
constructor(props) {
super(props);
const { columnName } = this.props
this.state = {
columnNameValue: columnName ? columnName : '',
columnNameError: ''
}
this.handleColumnNameChange = this.handleColumnNameChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleColumnNameChange(event) {
this.setState({ columnNameValue: event.target.value });
}
handleSubmit(submit) {
const { columns, onSubmit } = this.props
const { columnNameValue } = this.state
if (submit) {
const keys = columns.map(column => { return column.key })
if (!columnNameValue) {
this.setState({ columnNameError: 'Please give a column name.' })
} else if (keys.indexOf(columnNameValue) > -1) {
this.setState({ columnNameError: 'A column with this title already exists.' })
} else {
this.setState({ columnNameError: '', columnNameValue: '' })
onSubmit(columnNameValue)
}
} else {
this.setState({ columnNameError: '', columnNameValue: '' })
onSubmit()
}
}
render() {
const { showModal } = this.props
const { columnNameValue, columnNameError } = this.state
let show, title
if (showModal == 'insert') {
show = true
title = 'Insert column'
} else if (showModal == 'rename') {
show = true
title = 'Rename column'
}
let validationState = columnNameError ? 'error' : null
return (
<Modal animation show={show}>
<Modal.Header closeButton>
<Modal.Title>
{title}
</Modal.Title>
</Modal.Header>
<Modal.Body >
<div>
<FormGroup validationState={validationState}>
<ControlLabel>Colum name</ControlLabel>
<FormControl
type="text"
value={columnNameValue}
onChange={this.handleColumnNameChange}
/>
<HelpBlock>{columnNameError}</HelpBlock>
</FormGroup>
</div>
<div>
<ButtonToolbar>
<Button bsStyle="warning" onClick={event => this.handleSubmit(false)}>
Cancel
</Button>
<Button bsStyle="primary" onClick={event => this.handleSubmit(true)}>
{title}
</Button>
</ButtonToolbar>
</div>
</Modal.Body>
</Modal>
)