Brandon Nicholls

RESCOI-516: Allows ordering of relationship matrix items

......@@ -17,7 +17,6 @@
*/
import styles from './style';
import classNames from 'classnames';
import React from 'react';
import CheckmarkIcon from '../../dynamic-icons/checkmark-icon';
import {RETURN_KEY} from '../../../../../coi-constants';
......@@ -34,6 +33,28 @@ export default class EditableItem extends React.Component {
this.edit = this.edit.bind(this);
this.keyPressed = this.keyPressed.bind(this);
this.done = this.done.bind(this);
this.moveUp = this.moveUp.bind(this);
this.moveDown = this.moveDown.bind(this);
}
moveUp() {
this.refs.container.classList.add(styles.up);
this.refs.container.previousSibling.classList.add(styles.down);
setTimeout(() => {
this.refs.container.classList.remove(styles.up);
this.refs.container.previousSibling.classList.remove(styles.down);
this.props.onMoveUp(this.props.index);
}, 100);
}
moveDown() {
this.refs.container.classList.add(styles.down);
this.refs.container.nextSibling.classList.add(styles.up);
setTimeout(() => {
this.refs.container.classList.remove(styles.down);
this.refs.container.nextSibling.classList.remove(styles.up);
this.props.onMoveDown(this.props.index);
}, 100);
}
edit() {
......@@ -50,7 +71,7 @@ export default class EditableItem extends React.Component {
}
delete() {
this.props.onDelete(this.props.id);
this.props.onDelete(this.props.index);
}
keyPressed(evt) {
......@@ -65,14 +86,14 @@ export default class EditableItem extends React.Component {
editing: false
});
const textbox = this.refs.textbox;
this.props.onEdit(this.props.id, this.props.typeCd, textbox.value);
this.props.onEdit(this.props.index, this.props.typeCd, textbox.value);
}
render() {
let content;
if (this.state.editing) {
content = (
<div className={classNames(styles.container, this.props.className)}>
<div className={styles.container}>
<span onClick={this.done} className={styles.done}>
<CheckmarkIcon className={`${styles.override} ${styles.checkmark}`} color="#32A03C" />
Done
......@@ -88,8 +109,31 @@ export default class EditableItem extends React.Component {
);
}
else {
let upSytle;
let downStyle;
if (this.props.index === 0) {
upSytle = {display: 'none'};
}
if (this.props.last) {
downStyle = {display: 'none'};
}
content = (
<div className={classNames(styles.container, this.props.className)}>
<div className={styles.container} ref="container">
<div style={{width: '50px', float: 'left'}}>
<div style={{display: 'inline-block', width: '45%'}}>
<button className={styles.button} style={upSytle} onClick={this.moveUp}>
<i className={'fa fa-arrow-up'} />
</button>
</div>
<div style={{display: 'inline-block', width: '45%'}}>
<button className={styles.button} style={downStyle} onClick={this.moveDown}>
<i className={'fa fa-arrow-down'} />
</button>
</div>
</div>
<i className={`fa fa-pencil ${styles.editIcon}`} onClick={this.edit} />
<span className={styles.deleteIcon} onClick={this.delete}>X</span>
<span className={styles.text}>{this.props.children}</span>
......@@ -100,3 +144,23 @@ export default class EditableItem extends React.Component {
return content;
}
}
EditableItem.PropTypes = {
children: React.PropTypes.array,
index: React.PropTypes.number.isRequired,
last: React.PropTypes.bool,
onDelete: React.PropTypes.func,
onEdit: React.PropTypes.func,
onMoveDown: React.PropTypes.func,
onMoveUp: React.PropTypes.func,
typeCd: React.PropTypes.number.isRequired
};
EditableItem.defaultProps = {
children: [],
last: false,
onDelete: () => {},
onEdit: () => {},
onMoveDown: () => {},
onMoveUp: () => {}
};
......
......@@ -81,3 +81,23 @@
:global(.color-blind) .done {
color: black;
}
.button {
color: #0095A0;
border: none;
background-color: transparent;
}
:global(.color-blind) .button {
color: black;
}
.down {
transform: translateY(28px);
transition: transform 0.1s ease-out;
}
.up {
transform: translateY(-28px);
transition: transform 0.1s ease-out;
}
......
......@@ -22,6 +22,11 @@ import CheckmarkIcon from '../../dynamic-icons/checkmark-icon';
import {RETURN_KEY} from '../../../../../coi-constants';
import EditableItem from '../editable-item';
function reassignOrders(items) {
items.forEach((item, index) => { item.order = index; });
return items;
}
export default class EditableList extends React.Component {
constructor() {
super();
......@@ -34,6 +39,8 @@ export default class EditableList extends React.Component {
this.cancel = this.cancel.bind(this);
this.keyPressed = this.keyPressed.bind(this);
this.edited = this.edited.bind(this);
this.moveUp = this.moveUp.bind(this);
this.moveDown = this.moveDown.bind(this);
}
add() {
......@@ -49,10 +56,34 @@ export default class EditableList extends React.Component {
});
}
moveUp(index) {
if (index > 0) {
const swapItem = this.props.items[index - 1];
this.props.items[index - 1] = this.props.items[index];
this.props.items[index] = swapItem;
reassignOrders(this.props.items);
this.props.onChange(this.props.items);
}
}
moveDown(index) {
if (index >= 0 && index < this.props.items.length - 1) {
const swapItem = this.props.items[index + 1];
this.props.items[index + 1] = this.props.items[index];
this.props.items[index] = swapItem;
reassignOrders(this.props.items);
this.props.onChange(this.props.items);
}
}
delete(id) {
const newItems = Array.from(this.props.items);
let newItems = Array.from(this.props.items);
newItems.splice(id, 1);
newItems = reassignOrders(newItems);
this.props.onChange(newItems);
}
......@@ -88,11 +119,14 @@ export default class EditableList extends React.Component {
addItem() {
const textbox = this.refs.textbox;
if (textbox.value.length > 0) {
const newItems = Array.from(this.props.items);
let newItems = Array.from(this.props.items);
newItems.push({
description: textbox.value
});
newItems = reassignOrders(newItems);
textbox.value = '';
this.props.onChange(newItems);
......@@ -100,30 +134,46 @@ export default class EditableList extends React.Component {
}
render() {
let items;
if (this.props.items) {
items = this.props.items.map((item, index) => {
const items = this.props.items
.sort((a, b) => a.order - b.order)
.map((item, index, array) => {
return (
<EditableItem key={index} id={index} typeCd={item.typeCd} onDelete={this.delete} onEdit={this.edited}>
<EditableItem
key={index}
index={index}
typeCd={item.typeCd}
onDelete={this.delete}
onEdit={this.edited}
onMoveDown={this.moveDown}
onMoveUp={this.moveUp}
last={index === array.length - 1}
>
{item.description}
</EditableItem>
);
});
}
let addAnother;
if (this.state.adding) {
addAnother = (
<div style={{margin: '0 0 0 25px'}}>
<span onClick={this.done} className={styles.done}>
<CheckmarkIcon className={`${styles.override} ${styles.checkmark}`} color="#32A03C" />
<CheckmarkIcon
className={`${styles.override} ${styles.checkmark}`}
color="#32A03C"
/>
Done
</span>
<span onClick={this.cancel} className={styles.cancel}>
<span style={{fontWeight: 'bold', marginRight: 4}}>X</span>
Cancel
</span>
<input type="text" ref="textbox" className={styles.textbox} onKeyDown={this.keyPressed} />
<input
type="text"
ref="textbox"
className={styles.textbox}
onKeyDown={this.keyPressed}
/>
</div>
);
}
......@@ -146,3 +196,15 @@ export default class EditableList extends React.Component {
);
}
}
EditableList.PropTypes = {
className: React.PropTypes.string,
items: React.PropTypes.array,
onChange: React.PropTypes.func
};
EditableList.defaultProps = {
className: '',
items: [],
onChange: () => {}
};
......
/*
The Conflict of Interest (COI) module of Kuali Research
Copyright © 2005-2016 Kuali, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
/* eslint-disable prefer-arrow-callback, camelcase */
exports.up = function(knex) {
return knex.schema.table('relationship_type', function(table) {
table.integer('order').notNullable();
}).then(() => {
return knex
.select(
'type_cd',
'relationship_cd'
)
.from('relationship_type')
.where({
active: true
})
.orderBy('relationship_cd')
.orderBy('type_cd');
}).then(relationshipTypes => {
let i = -1;
return Promise.all(
relationshipTypes.map(rt => {
i++;
return knex('relationship_type')
.update('order', Number(i))
.where({type_cd: rt.type_cd});
})
);
}).then(() => {
return knex.schema.table('relationship_amount_type', function(table) {
table.integer('order').notNullable();
});
}).then(() => {
return knex
.select(
'type_cd',
'relationship_cd'
)
.from('relationship_amount_type')
.where({
active: true
})
.orderBy('relationship_cd')
.orderBy('type_cd');
}).then(amountTypes => {
let i = -1;
return Promise.all(
amountTypes.map(rt => {
i++;
return knex('relationship_amount_type')
.update('order', Number(i))
.where({type_cd: rt.type_cd});
})
);
}).then(() => {
return knex.schema.table('relationship_person_type', function(table) {
table.integer('order').notNullable();
});
}).then(() => {
return knex
.select('type_cd')
.from('relationship_person_type')
.where({
active: true
})
.orderBy('type_cd');
}).then(amountTypes => {
let i = -1;
return Promise.all(
amountTypes.map(rt => {
i++;
return knex('relationship_person_type')
.update('order', Number(i))
.where({type_cd: rt.type_cd});
})
);
});
};
exports.down = function() {
};
......@@ -210,11 +210,13 @@ async function createMatrixTypes(knex) {
const relationshipTypes = await knex
.select('*')
.from('relationship_type')
.where('active', true);
.where('active', true)
.orderBy('order');
const amountTypes = await knex
.select('*')
.from('relationship_amount_type')
.where('active', true);
.where('active', true)
.orderBy('order');
return categories.map(type => {
type.typeOptions = relationshipTypes.filter(relationType => {
return relationType.relationship_cd === type.type_cd;
......@@ -313,7 +315,8 @@ export async function getConfig(dbInfo, knex, hostname) {
config.relationshipPersonTypes = await knex
.select('*')
.from('relationship_person_type')
.where('active', true);
.where('active', true)
.orderBy('order');
config.declarationTypes = await knex
.select('*')
......