312 lines
8.4 KiB
JavaScript
312 lines
8.4 KiB
JavaScript
/* eslint-disable prefer-destructuring */
|
||
/*
|
||
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
|
||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||
*/
|
||
/* global d3, nv */
|
||
|
||
import AdapterBase from '../../../api/AdapterBase';
|
||
|
||
class CompanyStructureAdapter extends AdapterBase {
|
||
getDataMapping() {
|
||
return [
|
||
'id',
|
||
'title',
|
||
'address',
|
||
'type',
|
||
'country',
|
||
'timezone',
|
||
'parent',
|
||
];
|
||
}
|
||
|
||
getHeaders() {
|
||
return [
|
||
{ sTitle: 'ID', bVisible: false },
|
||
{ sTitle: 'Name' },
|
||
{ sTitle: 'Address', bSortable: false },
|
||
{ sTitle: 'Type' },
|
||
{ sTitle: 'Country', sClass: 'center' },
|
||
{ sTitle: 'Time Zone' },
|
||
{ sTitle: 'Parent Structure' },
|
||
];
|
||
}
|
||
|
||
getFormFields() {
|
||
return [
|
||
['id', { label: 'ID', type: 'hidden', validation: '' }],
|
||
['title', { label: 'Name', type: 'text', validation: '' }],
|
||
['description', { label: 'Details', type: 'textarea', validation: '' }],
|
||
['address', { label: 'Address', type: 'textarea', validation: 'none' }],
|
||
['type', { label: 'Type', type: 'select', source: [['Company', 'Company'], ['Head Office', 'Head Office'], ['Regional Office', 'Regional Office'], ['Department', 'Department'], ['Unit', 'Unit'], ['Sub Unit', 'Sub Unit'], ['Other', 'Other']] }],
|
||
['country', { label: 'Country', type: 'select2', 'remote-source': ['Country', 'code', 'name'] }],
|
||
['timezone', {
|
||
label: 'Time Zone', type: 'select2', 'allow-null': false, 'remote-source': ['Timezone', 'name', 'details'],
|
||
}],
|
||
['parent', {
|
||
label: 'Parent Structure', type: 'select', 'allow-null': true, 'remote-source': ['CompanyStructure', 'id', 'title'],
|
||
}],
|
||
['heads', {
|
||
label: 'Heads', type: 'select2multi', 'allow-null': true, 'remote-source': ['Employee', 'id', 'first_name+last_name'],
|
||
}],
|
||
];
|
||
}
|
||
|
||
postRenderForm(object, $tempDomObj) {
|
||
if (object === undefined
|
||
|| object === null
|
||
|| object.id === null
|
||
|| object.id === undefined || object.id === ''
|
||
) {
|
||
$tempDomObj.find('#field_heads').hide();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* Company Graph
|
||
*/
|
||
|
||
|
||
class CompanyGraphAdapter extends CompanyStructureAdapter {
|
||
constructor(endPoint, tab, filter, orderBy) {
|
||
super(endPoint, tab, filter, orderBy);
|
||
this.nodeIdCounter = 0;
|
||
}
|
||
|
||
convertToTree(data) {
|
||
const ice = {};
|
||
ice.id = -1;
|
||
ice.title = '';
|
||
ice.name = '';
|
||
ice.children = [];
|
||
|
||
let parent = null;
|
||
|
||
const added = {};
|
||
|
||
|
||
for (let i = 0; i < data.length; i++) {
|
||
data[i].name = data[i].title;
|
||
|
||
if (data[i].parent != null && data[i].parent !== undefined) {
|
||
parent = this.findParent(data, data[i].parent);
|
||
if (parent != null) {
|
||
if (parent.children === undefined || parent.children == null) {
|
||
parent.children = [];
|
||
}
|
||
parent.children.push(data[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
for (let i = 0; i < data.length; i++) {
|
||
if (data[i].parent == null || data[i].parent === undefined) {
|
||
ice.children.push(data[i]);
|
||
}
|
||
}
|
||
|
||
return ice;
|
||
}
|
||
|
||
|
||
findParent(data, parent) {
|
||
for (let i = 0; i < data.length; i++) {
|
||
if (data[i].title === parent || data[i].title === parent) {
|
||
return data[i];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
|
||
createTable(elementId) {
|
||
$('#tabPageCompanyGraph').html('');
|
||
const that = this;
|
||
const sourceData = this.sourceData;
|
||
|
||
// this.fixCyclicParent(sourceData);
|
||
const treeData = this.convertToTree(sourceData);
|
||
const m = [20, 120, 20, 120];
|
||
const w = 5000 - m[1] - m[3];
|
||
const h = 1000 - m[0] - m[2];
|
||
|
||
const tree = d3.layout.tree()
|
||
.size([h, w]);
|
||
|
||
this.diagonal = d3.svg.diagonal()
|
||
.projection(d => [d.y, d.x]);
|
||
|
||
this.vis = d3.select('#tabPageCompanyGraph').append('svg:svg')
|
||
.attr('width', w + m[1] + m[3])
|
||
.attr('height', h + m[0] + m[2])
|
||
.append('svg:g')
|
||
.attr('transform', `translate(${m[3]},${m[0]})`);
|
||
|
||
const root = treeData;
|
||
root.x0 = h / 2;
|
||
root.y0 = 0;
|
||
|
||
function toggleAll(d) {
|
||
if (d.children) {
|
||
console.log(d.name);
|
||
d.children.forEach(toggleAll);
|
||
that.toggle(d);
|
||
}
|
||
}
|
||
this.update(root, tree, root);
|
||
}
|
||
|
||
update(source, tree, root) {
|
||
const that = this;
|
||
const duration = d3.event && d3.event.altKey ? 5000 : 500;
|
||
|
||
// Compute the new tree layout.
|
||
const nodes = tree.nodes(root).reverse();
|
||
|
||
// Normalize for fixed-depth.
|
||
nodes.forEach((d) => { d.y = d.depth * 180; });
|
||
|
||
// Update the nodes<65>
|
||
const node = that.vis.selectAll('g.node')
|
||
.data(nodes, d => d.id || (d.id = ++that.nodeIdCounter));
|
||
|
||
// Enter any new nodes at the parent's previous position.
|
||
const nodeEnter = node.enter().append('svg:g')
|
||
.attr('class', 'node')
|
||
.attr('transform', d => `translate(${source.y0},${source.x0})`)
|
||
.on('click', (d) => { that.toggle(d); that.update(d, tree, root); });
|
||
|
||
nodeEnter.append('svg:circle')
|
||
.attr('r', 1e-6)
|
||
.style('fill', d => (d._children ? 'lightsteelblue' : '#fff'));
|
||
|
||
nodeEnter.append('svg:text')
|
||
.attr('x', d => (d.children || d._children ? -10 : 10))
|
||
.attr('dy', '.35em')
|
||
.attr('text-anchor', d => (d.children || d._children ? 'end' : 'start'))
|
||
.text(d => d.name)
|
||
.style('fill-opacity', 1e-6);
|
||
|
||
// Transition nodes to their new position.
|
||
const nodeUpdate = node.transition()
|
||
.duration(duration)
|
||
.attr('transform', d => `translate(${d.y},${d.x})`);
|
||
|
||
nodeUpdate.select('circle')
|
||
.attr('r', 4.5)
|
||
.style('fill', d => (d._children ? 'lightsteelblue' : '#fff'));
|
||
|
||
nodeUpdate.select('text')
|
||
.style('fill-opacity', 1);
|
||
|
||
// Transition exiting nodes to the parent's new position.
|
||
const nodeExit = node.exit().transition()
|
||
.duration(duration)
|
||
.attr('transform', d => `translate(${source.y},${source.x})`)
|
||
.remove();
|
||
|
||
nodeExit.select('circle')
|
||
.attr('r', 1e-6);
|
||
|
||
nodeExit.select('text')
|
||
.style('fill-opacity', 1e-6);
|
||
|
||
// Update the links<6B>
|
||
const link = that.vis.selectAll('path.link')
|
||
.data(tree.links(nodes), d => d.target.id);
|
||
|
||
// Enter any new links at the parent's previous position.
|
||
link.enter().insert('svg:path', 'g')
|
||
.attr('class', 'link')
|
||
.attr('d', (d) => {
|
||
const o = { x: source.x0, y: source.y0 };
|
||
return that.diagonal({ source: o, target: o });
|
||
})
|
||
.transition()
|
||
.duration(duration)
|
||
.attr('d', that.diagonal);
|
||
|
||
// Transition links to their new position.
|
||
link.transition()
|
||
.duration(duration)
|
||
.attr('d', that.diagonal);
|
||
|
||
// Transition exiting nodes to the parent's new position.
|
||
link.exit().transition()
|
||
.duration(duration)
|
||
.attr('d', (d) => {
|
||
const o = { x: source.x, y: source.y };
|
||
return that.diagonal({ source: o, target: o });
|
||
})
|
||
.remove();
|
||
|
||
// Stash the old positions for transition.
|
||
nodes.forEach((d) => {
|
||
d.x0 = d.x;
|
||
d.y0 = d.y;
|
||
});
|
||
}
|
||
|
||
// Toggle children.
|
||
toggle(d) {
|
||
if (d.children) {
|
||
d._children = d.children;
|
||
d.children = null;
|
||
} else {
|
||
d.children = d._children;
|
||
d._children = null;
|
||
}
|
||
}
|
||
|
||
|
||
getSourceDataById(id) {
|
||
for (let i = 0; i < this.sourceData.length; i++) {
|
||
if (this.sourceData[i].id == id) {
|
||
return this.sourceData[i];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
fixCyclicParent(sourceData) {
|
||
let errorMsg = '';
|
||
for (let i = 0; i < sourceData.length; i++) {
|
||
const obj = sourceData[i];
|
||
|
||
|
||
let curObj = obj;
|
||
const parentIdArr = {};
|
||
parentIdArr[curObj.id] = 1;
|
||
|
||
while (curObj.parent != null && curObj.parent != undefined) {
|
||
const parent = this.getSourceDataById(curObj.parent);
|
||
if (parent == null) {
|
||
break;
|
||
} else if (parentIdArr[parent.id] == 1) {
|
||
errorMsg = `${obj.title}'s parent structure set to ${parent.title}<br/>`;
|
||
obj.parent = null;
|
||
break;
|
||
}
|
||
parentIdArr[parent.id] = 1;
|
||
curObj = parent;
|
||
}
|
||
}
|
||
|
||
if (errorMsg !== '') {
|
||
this.showMessage('Company Structure is having a cyclic dependency', `We found a cyclic dependency due to following reasons:<br/>${errorMsg}`);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
getHelpLink() {
|
||
return 'https://thilinah.gitbooks.io/icehrm-guide/content/employee-information-setup.html';
|
||
}
|
||
}
|
||
|
||
module.exports = { CompanyStructureAdapter, CompanyGraphAdapter };
|