Merge branch '1625-worker_department_treeview' of verdnatura/salix into dev
gitea/salix/dev This commit looks good
Details
gitea/salix/dev This commit looks good
Details
GJ
This commit is contained in:
commit
4b235faa8a
|
@ -0,0 +1,83 @@
|
||||||
|
ALTER TABLE `vn2008`.`department`
|
||||||
|
ADD COLUMN `parentFk` INT UNSIGNED NULL AFTER `sons`,
|
||||||
|
ADD COLUMN `path` VARCHAR(255) NULL AFTER `parentFk`,
|
||||||
|
CHANGE COLUMN `sons` `sons` DECIMAL(10,0) NOT NULL DEFAULT '0' ;
|
||||||
|
|
||||||
|
USE `vn`;
|
||||||
|
CREATE
|
||||||
|
OR REPLACE ALGORITHM = UNDEFINED
|
||||||
|
DEFINER = `root`@`%`
|
||||||
|
SQL SECURITY DEFINER
|
||||||
|
VIEW `department` AS
|
||||||
|
SELECT
|
||||||
|
`b`.`department_id` AS `id`,
|
||||||
|
`b`.`name` AS `name`,
|
||||||
|
`b`.`production` AS `isProduction`,
|
||||||
|
`b`.`parentFk` AS `parentFk`,
|
||||||
|
`b`.`path` AS `path`,
|
||||||
|
`b`.`lft` AS `lft`,
|
||||||
|
`b`.`rgt` AS `rgt`,
|
||||||
|
`b`.`isSelected` AS `isSelected`,
|
||||||
|
`b`.`depth` AS `depth`,
|
||||||
|
`b`.`sons` AS `sons`
|
||||||
|
FROM
|
||||||
|
`vn2008`.`department` `b`;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `vn2008`.`department_AFTER_DELETE`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn2008`$$
|
||||||
|
CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_AFTER_DELETE`
|
||||||
|
AFTER DELETE ON `department` FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `vn2008`.`department_BEFORE_INSERT`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn2008`$$
|
||||||
|
CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_BEFORE_INSERT`
|
||||||
|
BEFORE INSERT ON `department` FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `vn2008`.`department_AFTER_UPDATE`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn2008`$$
|
||||||
|
CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_AFTER_UPDATE`
|
||||||
|
AFTER UPDATE ON `department` FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF !(OLD.parentFk <=> NEW.parentFk) THEN
|
||||||
|
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CREATE TABLE `vn`.`department_recalc` (
|
||||||
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`isChanged` TINYINT(4) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`));
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`department_recalc` (`id`, `isChanged`) VALUES ('1', '0');
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `vn2008`.`department`
|
||||||
|
CHANGE COLUMN `lft` `lft` INT(11) NULL ,
|
||||||
|
CHANGE COLUMN `rgt` `rgt` INT(11) NULL ;
|
||||||
|
|
||||||
|
ALTER TABLE `vn2008`.`department`
|
||||||
|
DROP INDEX `rgt_UNIQUE` ,
|
||||||
|
DROP INDEX `lft_UNIQUE` ;
|
||||||
|
;
|
||||||
|
|
||||||
|
ALTER TABLE `vn2008`.`department`
|
||||||
|
ADD INDEX `lft_rgt_depth_idx` (`lft` ASC, `rgt` ASC, `depth` ASC);
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE vn.department SET lft = NULL, rgt = NULL;
|
|
@ -0,0 +1,37 @@
|
||||||
|
USE `vn`;
|
||||||
|
DROP procedure IF EXISTS `department_calcTree`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn`$$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `department_calcTree`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Calculates the #path, #lft, #rgt, #sons and #depth columns of
|
||||||
|
* the #department table. To build the tree, it uses the #parentFk
|
||||||
|
* column.
|
||||||
|
*/
|
||||||
|
DECLARE vIndex INT DEFAULT 0;
|
||||||
|
DECLARE vSons INT;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tNestedTree;
|
||||||
|
CREATE TEMPORARY TABLE tNestedTree
|
||||||
|
SELECT id, path, lft, rgt, depth, sons
|
||||||
|
FROM department LIMIT 0;
|
||||||
|
|
||||||
|
SET max_sp_recursion_depth = 5;
|
||||||
|
CALL department_calcTreeRec(NULL, '/', 0, vIndex, vSons);
|
||||||
|
SET max_sp_recursion_depth = 0;
|
||||||
|
|
||||||
|
UPDATE department z
|
||||||
|
JOIN tNestedTree t ON t.id = z.id
|
||||||
|
SET z.path = t.path,
|
||||||
|
z.lft = t.lft,
|
||||||
|
z.rgt = t.rgt,
|
||||||
|
z.depth = t.depth,
|
||||||
|
z.sons = t.sons;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tNestedTree;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
USE `vn`;
|
||||||
|
DROP procedure IF EXISTS `department_calcTreeRec`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn`$$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `department_calcTreeRec`(
|
||||||
|
vSelf INT,
|
||||||
|
vPath VARCHAR(255),
|
||||||
|
vDepth INT,
|
||||||
|
INOUT vIndex INT,
|
||||||
|
OUT vSons INT
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Calculates and sets the #path, #lft, #rgt, #sons and #depth
|
||||||
|
* columns for all children of the passed node. Once calculated
|
||||||
|
* the last node rgt index and the number of sons are returned.
|
||||||
|
* To update it's children, this procedure calls itself recursively
|
||||||
|
* for each one.
|
||||||
|
*
|
||||||
|
* @vSelf The node identifier
|
||||||
|
* @vPath The initial path
|
||||||
|
* @vDepth The initial depth
|
||||||
|
* @vIndex The initial lft index
|
||||||
|
* @vSons The number of direct sons
|
||||||
|
*/
|
||||||
|
DECLARE vChildFk INT;
|
||||||
|
DECLARE vLft INT;
|
||||||
|
DECLARE vMySons INT;
|
||||||
|
DECLARE vDone BOOL;
|
||||||
|
DECLARE vChildren CURSOR FOR
|
||||||
|
SELECT id FROM department
|
||||||
|
WHERE (vSelf IS NULL AND parentFk IS NULL)
|
||||||
|
OR (vSelf IS NOT NULL AND parentFk = vSelf);
|
||||||
|
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||||
|
|
||||||
|
SET vSons = 0;
|
||||||
|
|
||||||
|
OPEN vChildren;
|
||||||
|
myLoop: LOOP
|
||||||
|
SET vDone = FALSE;
|
||||||
|
FETCH vChildren INTO vChildFk;
|
||||||
|
|
||||||
|
IF vDone THEN
|
||||||
|
LEAVE myLoop;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SET vIndex = vIndex + 1;
|
||||||
|
SET vLft = vIndex;
|
||||||
|
SET vSons = vSons + 1;
|
||||||
|
|
||||||
|
CALL department_calcTreeRec(
|
||||||
|
vChildFk,
|
||||||
|
CONCAT(vPath, vChildFk, '/'),
|
||||||
|
vDepth + 1,
|
||||||
|
vIndex,
|
||||||
|
vMySons
|
||||||
|
);
|
||||||
|
|
||||||
|
SET vIndex = vIndex + 1;
|
||||||
|
|
||||||
|
INSERT INTO tNestedTree
|
||||||
|
SET id = vChildFk,
|
||||||
|
path = vPath,
|
||||||
|
lft = vLft,
|
||||||
|
rgt = vIndex,
|
||||||
|
depth = vDepth,
|
||||||
|
sons = vMySons;
|
||||||
|
END LOOP;
|
||||||
|
CLOSE vChildren;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
USE `vn`;
|
||||||
|
DROP procedure IF EXISTS `department_doCalc`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn`$$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `department_doCalc`()
|
||||||
|
proc: BEGIN
|
||||||
|
/**
|
||||||
|
* Recalculates the department tree.
|
||||||
|
*/
|
||||||
|
DECLARE vIsChanged BOOL;
|
||||||
|
|
||||||
|
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
|
||||||
|
BEGIN
|
||||||
|
DO RELEASE_LOCK('vn.department_doCalc');
|
||||||
|
RESIGNAL;
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF !GET_LOCK('vn.department_doCalc', 0) THEN
|
||||||
|
LEAVE proc;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT isChanged INTO vIsChanged
|
||||||
|
FROM department_recalc;
|
||||||
|
|
||||||
|
IF vIsChanged THEN
|
||||||
|
UPDATE department_recalc SET isChanged = FALSE;
|
||||||
|
CALL vn.department_calcTree;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DO RELEASE_LOCK('vn.department_doCalc');
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
USE `vn`;
|
||||||
|
DROP procedure IF EXISTS `department_getLeaves`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
USE `vn`$$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `department_getLeaves`(
|
||||||
|
vParentFk INT,
|
||||||
|
vSearch VARCHAR(255)
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
DECLARE vIsNumber BOOL;
|
||||||
|
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != '';
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tNodes;
|
||||||
|
CREATE TEMPORARY TABLE tNodes
|
||||||
|
(UNIQUE (id))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT id FROM department LIMIT 0;
|
||||||
|
|
||||||
|
IF vIsSearch THEN
|
||||||
|
SET vIsNumber = vSearch REGEXP '^[0-9]+$';
|
||||||
|
|
||||||
|
INSERT INTO tNodes
|
||||||
|
SELECT id FROM department
|
||||||
|
WHERE (vIsNumber AND `name` = vSearch)
|
||||||
|
OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%'))
|
||||||
|
LIMIT 1000;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF vParentFk IS NULL THEN
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tChilds;
|
||||||
|
CREATE TEMPORARY TABLE tChilds
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT id FROM tNodes;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tParents;
|
||||||
|
CREATE TEMPORARY TABLE tParents
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT id FROM department LIMIT 0;
|
||||||
|
|
||||||
|
myLoop: LOOP
|
||||||
|
DELETE FROM tParents;
|
||||||
|
INSERT INTO tParents
|
||||||
|
SELECT parentFk id
|
||||||
|
FROM department g
|
||||||
|
JOIN tChilds c ON c.id = g.id
|
||||||
|
WHERE g.parentFk IS NOT NULL;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO tNodes
|
||||||
|
SELECT id FROM tParents;
|
||||||
|
|
||||||
|
IF ROW_COUNT() = 0 THEN
|
||||||
|
LEAVE myLoop;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM tChilds;
|
||||||
|
INSERT INTO tChilds
|
||||||
|
SELECT id FROM tParents;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE
|
||||||
|
tChilds,
|
||||||
|
tParents;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF !vIsSearch THEN
|
||||||
|
INSERT IGNORE INTO tNodes
|
||||||
|
SELECT id FROM department
|
||||||
|
WHERE parentFk <=> vParentFk;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT d.id,
|
||||||
|
d.`name`,
|
||||||
|
d.parentFk,
|
||||||
|
d.sons
|
||||||
|
FROM department d
|
||||||
|
JOIN tNodes n ON n.id = d.id
|
||||||
|
ORDER BY depth, `name`;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tNodes;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -3,23 +3,28 @@ import './style.scss';
|
||||||
|
|
||||||
export default class IconButton {
|
export default class IconButton {
|
||||||
constructor($element) {
|
constructor($element) {
|
||||||
if ($element[0].getAttribute('tabindex') == null)
|
this.element = $element[0];
|
||||||
$element[0].tabIndex = 0;
|
|
||||||
|
|
||||||
$element.on('keyup', event => this.onKeyDown(event, $element));
|
if (this.element.getAttribute('tabindex') == null)
|
||||||
let button = $element[0].querySelector('button');
|
this.element.tabIndex = 0;
|
||||||
$element[0].addEventListener('click', event => {
|
|
||||||
if (this.disabled || button.disabled)
|
this.element.addEventListener('keyup', e => this.onKeyup(e));
|
||||||
event.stopImmediatePropagation();
|
this.element.addEventListener('click', e => this.onClick(e));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event, $element) {
|
onKeyup(event) {
|
||||||
|
if (event.code == 'Space')
|
||||||
|
this.onClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(event) {
|
||||||
if (event.defaultPrevented) return;
|
if (event.defaultPrevented) return;
|
||||||
if (event.keyCode == 13) {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
|
||||||
$element.triggerHandler('click');
|
// FIXME: Don't use Event.stopPropagation()
|
||||||
}
|
let button = this.element.querySelector('button');
|
||||||
|
if (this.disabled || button.disabled)
|
||||||
|
event.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,4 +45,3 @@ import './table';
|
||||||
import './td-editable';
|
import './td-editable';
|
||||||
import './th';
|
import './th';
|
||||||
import './treeview';
|
import './treeview';
|
||||||
import './treeview/child';
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
<ul ng-if="::$ctrl.items">
|
|
||||||
<li
|
|
||||||
ng-repeat="item in $ctrl.items"
|
|
||||||
on-drop="$ctrl.onDrop(item, dragged, dropped)"
|
|
||||||
vn-draggable="{{::$ctrl.draggable}}"
|
|
||||||
vn-droppable="{{::$ctrl.droppable}}"
|
|
||||||
ng-class="{expanded: item.active}">
|
|
||||||
<div
|
|
||||||
ng-click="$ctrl.toggle($event, item)"
|
|
||||||
class="node clickable">
|
|
||||||
<vn-icon
|
|
||||||
class="arrow"
|
|
||||||
ng-class="{invisible: item.sons == 0}"
|
|
||||||
icon="keyboard_arrow_down"
|
|
||||||
translate-attr="{title: 'Toggle'}">
|
|
||||||
</vn-icon>
|
|
||||||
<vn-check
|
|
||||||
vn-acl="{{$ctrl.aclRole}}"
|
|
||||||
ng-if="$ctrl.selectable"
|
|
||||||
field="item.selected"
|
|
||||||
disabled="$ctrl.disabled"
|
|
||||||
on-change="$ctrl.select(item, value)"
|
|
||||||
triple-state="true"
|
|
||||||
label="{{::item.name}}">
|
|
||||||
</vn-check>
|
|
||||||
<vn-icon-button
|
|
||||||
icon="{{icon.icon}}"
|
|
||||||
ng-repeat="icon in $ctrl.icons"
|
|
||||||
ng-click="$ctrl.onIconClick(icon, item, $ctrl.parent, $parent.$index)"
|
|
||||||
vn-acl="{{$ctrl.aclRole}}"
|
|
||||||
vn-acl-action="remove">
|
|
||||||
</vn-icon-button>
|
|
||||||
</div>
|
|
||||||
<vn-treeview-child
|
|
||||||
items="item.childs"
|
|
||||||
parent="item"
|
|
||||||
selectable="$ctrl.selectable"
|
|
||||||
disabled="$ctrl.disabled"
|
|
||||||
editable="$ctrl.editable"
|
|
||||||
draggable="::$ctrl.draggable"
|
|
||||||
droppable="::$ctrl.droppable"
|
|
||||||
icons="::$ctrl.icons"
|
|
||||||
parent-scope="::$ctrl.parentScope"
|
|
||||||
acl-role="$ctrl.aclRole">
|
|
||||||
</vn-treeview-child>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
ng-if="$ctrl.isInsertable && $ctrl.editable"
|
|
||||||
ng-click="$ctrl.onCreate($ctrl.parent)"
|
|
||||||
vn-acl="{{$ctrl.aclRole}}"
|
|
||||||
vn-acl-action="remove">
|
|
||||||
<div class="node">
|
|
||||||
<vn-icon-button
|
|
||||||
icon="add_circle">
|
|
||||||
</vn-icon-button>
|
|
||||||
<div class="description" translate>
|
|
||||||
Create new one
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
|
@ -1,56 +0,0 @@
|
||||||
import ngModule from '../../module';
|
|
||||||
import Component from '../../lib/component';
|
|
||||||
|
|
||||||
class Controller extends Component {
|
|
||||||
constructor($element, $scope) {
|
|
||||||
super($element, $scope);
|
|
||||||
this.$scope = $scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle(event, item) {
|
|
||||||
if (event.defaultPrevented || !item.sons) return;
|
|
||||||
event.preventDefault();
|
|
||||||
this.treeview.onToggle(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
select(item, value) {
|
|
||||||
this.treeview.onSelection(item, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
onIconClick(icon, item, parent, index) {
|
|
||||||
let parentController = this.parentScope.$ctrl;
|
|
||||||
icon.callback.call(parentController, item, parent, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
onCreate(parent) {
|
|
||||||
this.treeview.onCreate(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDrop(item, dragged, dropped) {
|
|
||||||
this.treeview.onDrop(item, dragged, dropped);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isInsertable() {
|
|
||||||
return Array.isArray(this.parent) || this.parent.childs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.component('vnTreeviewChild', {
|
|
||||||
template: require('./child.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
items: '<',
|
|
||||||
parent: '<',
|
|
||||||
icons: '<?',
|
|
||||||
disabled: '<?',
|
|
||||||
selectable: '<?',
|
|
||||||
editable: '<?',
|
|
||||||
draggable: '<?',
|
|
||||||
droppable: '<?',
|
|
||||||
aclRole: '<?',
|
|
||||||
parentScope: '<'
|
|
||||||
},
|
|
||||||
require: {
|
|
||||||
treeview: '^vnTreeview'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
<ul ng-if="$ctrl.items">
|
||||||
|
<li ng-repeat="item in $ctrl.items" >
|
||||||
|
<div
|
||||||
|
ng-class="{expanded: item.active}"
|
||||||
|
ng-click="$ctrl.onClick($event, item)"
|
||||||
|
class="node clickable">
|
||||||
|
<vn-icon
|
||||||
|
class="arrow"
|
||||||
|
ng-class="{invisible: !item.sons}"
|
||||||
|
icon="keyboard_arrow_down"
|
||||||
|
translate-attr="::{title: 'Toggle'}">
|
||||||
|
</vn-icon>
|
||||||
|
<vn-treeview-content
|
||||||
|
item="::item">
|
||||||
|
</vn-treeview-content>
|
||||||
|
<section class="buttons" ng-if="::!$ctrl.treeview.readOnly">
|
||||||
|
<vn-icon-button translate-attr="::{title: 'Remove'}"
|
||||||
|
icon="delete"
|
||||||
|
ng-click="$ctrl.treeview.onRemove(item)"
|
||||||
|
ng-if="item.parent">
|
||||||
|
</vn-icon-button>
|
||||||
|
<vn-icon-button translate-attr="::{title: 'Create'}"
|
||||||
|
icon="add_circle"
|
||||||
|
ng-click="$ctrl.treeview.onCreate(item)">
|
||||||
|
</vn-icon-button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<vn-treeview-childs
|
||||||
|
items="item.childs"
|
||||||
|
parent="::item">
|
||||||
|
</vn-treeview-childs>
|
||||||
|
</li>
|
||||||
|
</ul>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Component from '../../lib/component';
|
||||||
|
|
||||||
|
class Controller extends Component {
|
||||||
|
onClick(event, item) {
|
||||||
|
if (event.defaultPrevented || !item.sons) return;
|
||||||
|
event.preventDefault();
|
||||||
|
this.treeview.onToggle(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDrop(item, dragged, dropped) {
|
||||||
|
this.treeview.onDrop(item, dragged, dropped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnTreeviewChilds', {
|
||||||
|
template: require('./childs.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
items: '<',
|
||||||
|
parent: '<?'
|
||||||
|
},
|
||||||
|
require: {
|
||||||
|
treeview: '^vnTreeview'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
|
||||||
|
class Controller {
|
||||||
|
constructor($element, $scope, $compile) {
|
||||||
|
this.$element = $element;
|
||||||
|
this.$scope = $scope;
|
||||||
|
this.$compile = $compile;
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
if (this.item.parent) {
|
||||||
|
this.treeview.$transclude(($clone, $scope) => {
|
||||||
|
this.$contentScope = $scope;
|
||||||
|
$scope.item = this.item;
|
||||||
|
this.$element.append($clone);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let template = `<span translate>{{$ctrl.treeview.rootLabel}}</span>`;
|
||||||
|
let $clone = this.$compile(template)(this.$scope);
|
||||||
|
this.$element.append($clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$onDestroy() {
|
||||||
|
if (this.$contentScope)
|
||||||
|
this.$contentScope.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controller.$inject = ['$element', '$scope', '$compile'];
|
||||||
|
|
||||||
|
ngModule.component('vnTreeviewContent', {
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
item: '<'
|
||||||
|
},
|
||||||
|
require: {
|
||||||
|
treeview: '^vnTreeview'
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,13 +1,3 @@
|
||||||
<vn-treeview-child
|
<vn-treeview-childs
|
||||||
acl-role="$ctrl.aclRole"
|
items="$ctrl.items">
|
||||||
items="$ctrl.data"
|
</vn-treeview-childs>
|
||||||
parent="$ctrl.data"
|
|
||||||
selectable="$ctrl.selectable"
|
|
||||||
editable="$ctrl.editable"
|
|
||||||
disabled="$ctrl.disabled"
|
|
||||||
icons="$ctrl.icons"
|
|
||||||
parent-scope="$ctrl.$scope.$parent"
|
|
||||||
draggable="$ctrl.draggable"
|
|
||||||
droppable="$ctrl.droppable"
|
|
||||||
vn-droppable="{{$ctrl.droppable}}">
|
|
||||||
</vn-treeview-child>
|
|
||||||
|
|
|
@ -2,73 +2,122 @@ import ngModule from '../../module';
|
||||||
import Component from '../../lib/component';
|
import Component from '../../lib/component';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
|
import './childs';
|
||||||
|
import './content';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Treeview
|
* Treeview
|
||||||
*
|
|
||||||
* @property {String} position The relative position to the parent
|
|
||||||
*/
|
*/
|
||||||
export default class Treeview extends Component {
|
export default class Treeview extends Component {
|
||||||
constructor($element, $scope) {
|
constructor($element, $scope, $transclude) {
|
||||||
super($element, $scope);
|
super($element, $scope);
|
||||||
this.$scope = $scope;
|
this.$transclude = $transclude;
|
||||||
this.data = [];
|
this.readOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
get data() {
|
||||||
this.refresh();
|
return this._data;
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
set data(value) {
|
||||||
this.model.refresh().then(() => {
|
this._data = value;
|
||||||
this.data = this.model.data;
|
|
||||||
|
const sons = value.length;
|
||||||
|
const rootElement = [{
|
||||||
|
childs: value,
|
||||||
|
active: true,
|
||||||
|
sons: sons
|
||||||
|
}];
|
||||||
|
|
||||||
|
this.setParent(rootElement[0], value);
|
||||||
|
|
||||||
|
this.items = rootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch() {
|
||||||
|
return this.fetchFunc().then(res =>
|
||||||
|
this.data = res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setParent(parent, childs) {
|
||||||
|
childs.forEach(child => {
|
||||||
|
child.parent = parent;
|
||||||
|
|
||||||
|
if (child.childs)
|
||||||
|
this.setParent(parent, child.childs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits selection event
|
|
||||||
* @param {Object} item - Selected item
|
|
||||||
* @param {Boolean} value - Changed value
|
|
||||||
*/
|
|
||||||
onSelection(item, value) {
|
|
||||||
this.emit('selection', {item, value});
|
|
||||||
}
|
|
||||||
|
|
||||||
onCreate(parent) {
|
|
||||||
this.emit('create', {parent});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggle(item) {
|
onToggle(item) {
|
||||||
if (item.active)
|
if (item.active)
|
||||||
item.childs = undefined;
|
this.fold(item);
|
||||||
else {
|
else
|
||||||
this.model.applyFilter({}, {parentFk: item.id}).then(() => {
|
this.unfold(item);
|
||||||
const newData = this.model.data;
|
}
|
||||||
|
|
||||||
if (item.childs) {
|
fold(item) {
|
||||||
let childs = item.childs;
|
item.childs = undefined;
|
||||||
childs.forEach(child => {
|
item.active = false;
|
||||||
let index = newData.findIndex(newChild => {
|
}
|
||||||
return newChild.id == child.id;
|
|
||||||
});
|
unfold(item) {
|
||||||
newData[index] = child;
|
return this.fetchFunc({$item: item}).then(newData => {
|
||||||
|
this.setParent(item, newData);
|
||||||
|
|
||||||
|
const childs = item.childs;
|
||||||
|
if (childs) {
|
||||||
|
childs.forEach(child => {
|
||||||
|
let index = newData.findIndex(newChild => {
|
||||||
|
return newChild.id == child.id;
|
||||||
});
|
});
|
||||||
}
|
newData[index] = child;
|
||||||
|
|
||||||
item.childs = newData.sort((a, b) => {
|
|
||||||
if (b.selected !== a.selected) {
|
|
||||||
if (a.selected == null)
|
|
||||||
return 1;
|
|
||||||
if (b.selected == null)
|
|
||||||
return -1;
|
|
||||||
return b.selected - a.selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (this.sortFunc) {
|
||||||
|
item.childs = newData.sort((a, b) =>
|
||||||
|
this.sortFunc({$a: a, $b: b})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}).then(() => item.active = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove(item) {
|
||||||
|
if (this.removeFunc)
|
||||||
|
this.removeFunc({$item: item});
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(item) {
|
||||||
|
const parent = item.parent;
|
||||||
|
let childs = parent.childs;
|
||||||
|
|
||||||
|
if (!childs) childs = [];
|
||||||
|
|
||||||
|
let index = childs.indexOf(item);
|
||||||
|
childs.splice(index, 1);
|
||||||
|
if (parent) parent.sons--;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreate(parent) {
|
||||||
|
if (this.createFunc)
|
||||||
|
this.createFunc({$parent: parent});
|
||||||
|
}
|
||||||
|
|
||||||
|
create(item) {
|
||||||
|
const parent = item.parent;
|
||||||
|
let childs = parent.childs;
|
||||||
|
if (!childs) childs = [];
|
||||||
|
|
||||||
|
childs.push(item);
|
||||||
|
|
||||||
|
if (this.sortFunc) {
|
||||||
|
childs = childs.sort((a, b) =>
|
||||||
|
this.sortFunc({$a: a, $b: b})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.active = !item.active;
|
if (parent) parent.sons++;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(item, dragged, dropped) {
|
onDrop(item, dragged, dropped) {
|
||||||
|
@ -76,19 +125,21 @@ export default class Treeview extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Treeview.$inject = ['$element', '$scope'];
|
Treeview.$inject = ['$element', '$scope', '$transclude'];
|
||||||
|
|
||||||
ngModule.component('vnTreeview', {
|
ngModule.component('vnTreeview', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Treeview,
|
controller: Treeview,
|
||||||
bindings: {
|
bindings: {
|
||||||
model: '<',
|
rootLabel: '@',
|
||||||
icons: '<?',
|
data: '<?',
|
||||||
disabled: '<?',
|
|
||||||
selectable: '<?',
|
|
||||||
editable: '<?',
|
|
||||||
draggable: '<?',
|
draggable: '<?',
|
||||||
droppable: '<?',
|
droppable: '<?',
|
||||||
aclRole: '@?'
|
fetchFunc: '&',
|
||||||
}
|
removeFunc: '&?',
|
||||||
|
createFunc: '&?',
|
||||||
|
sortFunc: '&?',
|
||||||
|
readOnly: '<?'
|
||||||
|
},
|
||||||
|
transclude: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
@import "effects";
|
@import "effects";
|
||||||
|
|
||||||
vn-treeview {
|
vn-treeview-childs {
|
||||||
vn-treeview-child {
|
display: block;
|
||||||
display: block
|
|
||||||
}
|
|
||||||
ul {
|
ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -11,37 +10,43 @@ vn-treeview {
|
||||||
li {
|
li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
& > div > .arrow {
|
|
||||||
min-width: 24px;
|
|
||||||
margin-right: 10px;
|
|
||||||
transition: transform 200ms;
|
|
||||||
}
|
|
||||||
&.expanded > div > .arrow {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
& > .node {
|
& > .node {
|
||||||
@extend %clickable;
|
@extend %clickable;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
& > vn-check:not(.indeterminate) {
|
|
||||||
color: $color-main;
|
& > div > .arrow {
|
||||||
|
min-width: 24px;
|
||||||
& > .btn {
|
margin-right: 10px;
|
||||||
border-color: $color-main;
|
transition: transform 200ms;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
& > vn-check.checked {
|
& > div.expanded > .arrow {
|
||||||
color: $color-main;
|
transform: rotate(180deg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
padding-left: 2.2em;
|
padding-left: 2.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vn-icon-button {
|
vn-icon-button {
|
||||||
padding: 0
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node > .buttons {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.node:hover > .buttons {
|
||||||
|
display: block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vn-treeview-content {
|
||||||
|
flex-grow: 1
|
||||||
|
}
|
|
@ -107,5 +107,6 @@
|
||||||
"Invalid quantity": "Cantidad invalida",
|
"Invalid quantity": "Cantidad invalida",
|
||||||
"This postal code is not valid": "This postal code is not valid",
|
"This postal code is not valid": "This postal code is not valid",
|
||||||
"is invalid": "is invalid",
|
"is invalid": "is invalid",
|
||||||
"The postcode doesn't exists. Ensure you put the correct format": "El código postal no existe. Asegúrate de ponerlo con el formato correcto"
|
"The postcode doesn't exists. Ensure you put the correct format": "El código postal no existe. Asegúrate de ponerlo con el formato correcto",
|
||||||
|
"The department name can't be repeated": "El nombre del departamento no puede repetirse"
|
||||||
}
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('toggleIsIncluded', {
|
|
||||||
description: 'Toggle include to delivery',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'zoneFk',
|
|
||||||
type: 'Number',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'item',
|
|
||||||
type: 'Object',
|
|
||||||
required: true,
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/toggleIsIncluded`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.toggleIsIncluded = async(zoneFk, item) => {
|
|
||||||
if (item.isIncluded === null)
|
|
||||||
return Self.destroyAll({zoneFk, geoFk: item.id});
|
|
||||||
else {
|
|
||||||
return Self.upsert({
|
|
||||||
zoneFk: zoneFk,
|
|
||||||
geoFk: item.id,
|
|
||||||
isIncluded: item.isIncluded
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -6,10 +6,11 @@ module.exports = Self => {
|
||||||
{
|
{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
|
description: 'The zone id',
|
||||||
http: {source: 'path'},
|
http: {source: 'path'},
|
||||||
required: true
|
required: true
|
||||||
}, {
|
}, {
|
||||||
arg: 'parentFk',
|
arg: 'parentId',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
description: 'Get the children of the specified father',
|
description: 'Get the children of the specified father',
|
||||||
}, {
|
}, {
|
||||||
|
@ -28,10 +29,10 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.getLeaves = async(id, parentFk = null, search) => {
|
Self.getLeaves = async(id, parentId = null, search) => {
|
||||||
let [res] = await Self.rawSql(
|
let [res] = await Self.rawSql(
|
||||||
`CALL zone_getLeaves(?, ?, ?)`,
|
`CALL zone_getLeaves(?, ?, ?)`,
|
||||||
[id, parentFk, search]
|
[id, parentId, search]
|
||||||
);
|
);
|
||||||
|
|
||||||
let map = new Map();
|
let map = new Map();
|
||||||
|
@ -49,7 +50,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let leaves = map.get(parentFk);
|
let leaves = map.get(parentId);
|
||||||
setLeaves(leaves);
|
setLeaves(leaves);
|
||||||
|
|
||||||
return leaves || [];
|
return leaves || [];
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('toggleIsIncluded', {
|
||||||
|
description: 'Toggle include to delivery',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The zone id',
|
||||||
|
http: {source: 'path'},
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
arg: 'geoId',
|
||||||
|
type: 'Number',
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
arg: 'isIncluded',
|
||||||
|
type: 'Boolean'
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/toggleIsIncluded`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.toggleIsIncluded = async(id, geoId, isIncluded) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
if (isIncluded === undefined)
|
||||||
|
return models.ZoneIncluded.destroyAll({zoneFk: id, geoFk: geoId});
|
||||||
|
else {
|
||||||
|
return models.ZoneIncluded.upsert({
|
||||||
|
zoneFk: id,
|
||||||
|
geoFk: geoId,
|
||||||
|
isIncluded: isIncluded
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
require('../methods/zone-included/toggleIsIncluded')(Self);
|
|
||||||
};
|
|
|
@ -3,6 +3,7 @@ module.exports = Self => {
|
||||||
require('../methods/zone/editPrices')(Self);
|
require('../methods/zone/editPrices')(Self);
|
||||||
require('../methods/zone/getLeaves')(Self);
|
require('../methods/zone/getLeaves')(Self);
|
||||||
require('../methods/zone/getEvents')(Self);
|
require('../methods/zone/getEvents')(Self);
|
||||||
|
require('../methods/zone/toggleIsIncluded')(Self);
|
||||||
|
|
||||||
Self.validatesPresenceOf('agencyModeFk', {
|
Self.validatesPresenceOf('agencyModeFk', {
|
||||||
message: `Agency cannot be blank`
|
message: `Agency cannot be blank`
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
url="/api/Zones/{{$ctrl.$stateParams.id}}/getLeaves"
|
url="/api/Zones/{{$ctrl.$stateParams.id}}/getLeaves"
|
||||||
filter="::$ctrl.filter"
|
filter="::$ctrl.filter">
|
||||||
auto-load="false">
|
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<div class="vn-w-md">
|
<div class="vn-w-md">
|
||||||
<vn-card class="vn-px-lg">
|
<vn-card class="vn-px-lg">
|
||||||
<vn-searchbar
|
<vn-searchbar auto-load="false"
|
||||||
on-search="$ctrl.onSearch($params)"
|
on-search="$ctrl.onSearch($params)"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-searchbar>
|
</vn-searchbar>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-card class="vn-pa-lg vn-mt-md">
|
<vn-card class="vn-pa-lg vn-mt-md">
|
||||||
<vn-treeview
|
<vn-treeview vn-id="treeview" root-label="Locations"
|
||||||
vn-id="treeview"
|
fetch-func="$ctrl.onFetch($item)"
|
||||||
model="model"
|
sort-func="$ctrl.onSort($a, $b)">
|
||||||
acl-role="deliveryBoss"
|
<vn-check
|
||||||
on-selection="$ctrl.onSelection(item, value)"
|
field="item.selected"
|
||||||
selectable="true">
|
on-change="$ctrl.onSelection(value, item)"
|
||||||
|
triple-state="true"
|
||||||
|
label="{{::item.name}}">
|
||||||
|
</vn-check>
|
||||||
</vn-treeview>
|
</vn-treeview>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,32 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
import Section from 'core/lib/section';
|
import Section from 'core/lib/section';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
onSearch(params) {
|
onSearch(params) {
|
||||||
this.$.model.applyFilter(null, params);
|
this.$.model.applyFilter({}, params).then(() => {
|
||||||
this.$.$applyAsync(() => this.$.treeview.refresh());
|
const data = this.$.model.data;
|
||||||
|
this.$.treeview.data = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFetch(item) {
|
||||||
|
const params = item ? {parentId: item.id} : null;
|
||||||
|
return this.$.model.applyFilter({}, params).then(() => {
|
||||||
|
return this.$.model.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSort(a, b) {
|
||||||
|
if (b.selected !== a.selected) {
|
||||||
|
if (a.selected == null)
|
||||||
|
return 1;
|
||||||
|
if (b.selected == null)
|
||||||
|
return -1;
|
||||||
|
return b.selected - a.selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
exprBuilder(param, value) {
|
exprBuilder(param, value) {
|
||||||
|
@ -14,13 +36,11 @@ class Controller extends Section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelection(item, isIncluded) {
|
onSelection(value, item) {
|
||||||
let node = Object.assign({}, item);
|
if (value == null)
|
||||||
node.isIncluded = isIncluded;
|
value = undefined;
|
||||||
node.childs = []; // Data too large
|
const params = {geoId: item.id, isIncluded: value};
|
||||||
|
const path = `/api/zones/${this.zone.id}/toggleIsIncluded`;
|
||||||
const path = '/agency/api/ZoneIncludeds/toggleIsIncluded';
|
|
||||||
const params = {zoneFk: this.zone.id, item: node};
|
|
||||||
this.$http.post(path, params);
|
this.$http.post(path, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
vn-treeview-content {
|
||||||
|
& > vn-check:not(.indeterminate) {
|
||||||
|
color: $color-main;
|
||||||
|
|
||||||
|
& > .btn {
|
||||||
|
border-color: $color-main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > vn-check.checked {
|
||||||
|
color: $color-main;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('createChild', {
|
||||||
|
description: 'Creates a new child department',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'parentId',
|
||||||
|
type: 'Number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'name',
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/createChild`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.createChild = async(parentId = null, name) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const nameExists = await models.Department.count({name});
|
||||||
|
|
||||||
|
if (nameExists)
|
||||||
|
throw new UserError(`The department name can't be repeated`);
|
||||||
|
|
||||||
|
const newDep = await models.Department.create({
|
||||||
|
parentFk: parentId,
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
|
||||||
|
return newDep;
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,20 +1,14 @@
|
||||||
|
|
||||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('getLeaves', {
|
Self.remoteMethod('getLeaves', {
|
||||||
description: 'Returns the first shipped and landed possible for params',
|
description: 'Returns the nodes for a department',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'parentFk',
|
arg: 'parentId',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
description: 'Get the children of the specified father',
|
||||||
required: false,
|
}, {
|
||||||
},
|
arg: 'search',
|
||||||
{
|
type: 'String',
|
||||||
arg: 'filter',
|
description: 'Filter nodes whose name starts with',
|
||||||
type: 'Object',
|
|
||||||
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
|
||||||
http: {source: 'query'}
|
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['object'],
|
type: ['object'],
|
||||||
|
@ -26,61 +20,30 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.getLeaves = async(parentFk = 1, filter) => {
|
Self.getLeaves = async(parentId = null, search) => {
|
||||||
let conn = Self.dataSource.connector;
|
let [res] = await Self.rawSql(
|
||||||
let stmt = new ParameterizedSQL(
|
`CALL department_getLeaves(?, ?)`,
|
||||||
`SELECT
|
[parentId, search]
|
||||||
child.id,
|
);
|
||||||
child.name,
|
|
||||||
child.lft,
|
|
||||||
child.rgt,
|
|
||||||
child.depth,
|
|
||||||
child.sons
|
|
||||||
FROM department parent
|
|
||||||
JOIN department child ON child.lft > parent.lft
|
|
||||||
AND child.rgt < parent.rgt
|
|
||||||
AND child.depth = parent.depth + 1
|
|
||||||
WHERE parent.id = ?`, [parentFk]);
|
|
||||||
|
|
||||||
// Get nodes from depth greather than Origin
|
let map = new Map();
|
||||||
stmt.merge(conn.makeSuffix(filter));
|
for (let node of res) {
|
||||||
|
if (!map.has(node.parentFk))
|
||||||
const nodes = await Self.rawStmt(stmt);
|
map.set(node.parentFk, []);
|
||||||
|
map.get(node.parentFk).push(node);
|
||||||
if (nodes.length == 0)
|
|
||||||
return nodes;
|
|
||||||
|
|
||||||
// Get parent nodes
|
|
||||||
const minorDepth = nodes.reduce((a, b) => {
|
|
||||||
return b < a ? b : a;
|
|
||||||
}).depth;
|
|
||||||
|
|
||||||
const parentNodes = nodes.filter(element => {
|
|
||||||
return element.depth === minorDepth;
|
|
||||||
});
|
|
||||||
const leaves = Object.assign([], parentNodes);
|
|
||||||
|
|
||||||
nestLeaves(leaves);
|
|
||||||
|
|
||||||
function nestLeaves(elements) {
|
|
||||||
elements.forEach(element => {
|
|
||||||
let childs = Object.assign([], getLeaves(element));
|
|
||||||
if (childs.length > 0) {
|
|
||||||
element.childs = childs;
|
|
||||||
nestLeaves(element.childs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLeaves(parent) {
|
function setLeaves(nodes) {
|
||||||
let elements = nodes.filter(element => {
|
if (!nodes) return;
|
||||||
return element.lft > parent.lft && element.rgt < parent.rgt
|
for (let node of nodes) {
|
||||||
&& element.depth === parent.depth + 1;
|
node.childs = map.get(node.id);
|
||||||
});
|
setLeaves(node.childs);
|
||||||
|
}
|
||||||
return elements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return leaves;
|
let leaves = map.get(parentId);
|
||||||
|
setLeaves(leaves);
|
||||||
|
|
||||||
|
return leaves || [];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
|
|
||||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
|
||||||
|
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('nodeAdd', {
|
|
||||||
description: 'Returns the first shipped and landed possible for params',
|
|
||||||
accessType: 'WRITE',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'parentFk',
|
|
||||||
type: 'Number',
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'name',
|
|
||||||
type: 'String',
|
|
||||||
required: true,
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/nodeAdd`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.nodeAdd = async(parentFk = 1, name) => {
|
|
||||||
let stmts = [];
|
|
||||||
let conn = Self.dataSource.connector;
|
|
||||||
let nodeIndex = stmts.push(new ParameterizedSQL(
|
|
||||||
`CALL nst.nodeAdd('vn', 'department', ?, ?)`, [parentFk, name])) - 1;
|
|
||||||
|
|
||||||
stmts.push(`CALL nst.nodeRecalc('vn', 'department')`);
|
|
||||||
|
|
||||||
|
|
||||||
let sql = ParameterizedSQL.join(stmts, ';');
|
|
||||||
let result = await conn.executeStmt(sql);
|
|
||||||
let [node] = result[nodeIndex];
|
|
||||||
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
|
||||||
|
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('nodeDelete', {
|
|
||||||
description: 'Returns the first shipped and landed possible for params',
|
|
||||||
accessType: 'WRITE',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'parentFk',
|
|
||||||
type: 'Number',
|
|
||||||
required: false,
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: ['object'],
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/nodeDelete`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.nodeDelete = async parentFk => {
|
|
||||||
let stmt = new ParameterizedSQL(
|
|
||||||
`CALL nst.nodeDelete('vn', 'department', ?)`, [parentFk]);
|
|
||||||
|
|
||||||
return await Self.rawStmt(stmt);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('removeChild', {
|
||||||
|
description: 'Removes a child department',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The department id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: 'Object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/removeChild`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.removeChild = async id => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const department = await models.Department.findById(id);
|
||||||
|
|
||||||
|
return await department.destroy();
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,15 +1,5 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/department/getLeaves')(Self);
|
require('../methods/department/getLeaves')(Self);
|
||||||
require('../methods/department/nodeAdd')(Self);
|
require('../methods/department/createChild')(Self);
|
||||||
require('../methods/department/nodeDelete')(Self);
|
require('../methods/department/removeChild')(Self);
|
||||||
|
|
||||||
Self.rewriteDbError(function(err) {
|
|
||||||
if (err.code === 'ER_ROW_IS_REFERENCED_2')
|
|
||||||
return new UserError(`You cannot remove this department`);
|
|
||||||
if (err.code === 'ER_DUP_ENTRY')
|
|
||||||
return new UserError(`The department name can't be repeated`);
|
|
||||||
return err;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "String"
|
"type": "String"
|
||||||
|
},
|
||||||
|
"parentFk": {
|
||||||
|
"type": "Number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
url="/worker/api/departments/getLeaves"
|
url="/worker/api/departments/getLeaves"
|
||||||
params="::$ctrl.params"
|
|
||||||
auto-load="false">
|
auto-load="false">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
|
<div class="content-block" compact>
|
||||||
<form name="form">
|
<form name="form" compact>
|
||||||
<div class="vn-ma-md">
|
|
||||||
<vn-card class="vn-my-md vn-pa-md">
|
<vn-card class="vn-my-md vn-pa-md">
|
||||||
<vn-treeview vn-id="treeview" model="model"
|
<vn-treeview vn-id="treeview" root-label="Departments" read-only="false"
|
||||||
on-selection="$ctrl.onSelection(item, value)"
|
fetch-func="$ctrl.onFetch($item)"
|
||||||
on-create="$ctrl.onCreate(parent)"
|
remove-func="$ctrl.onRemove($item)"
|
||||||
on-drop="$ctrl.onDrop(item, dragged, dropped)"
|
create-func="$ctrl.onCreate($parent)"
|
||||||
icons="$ctrl.icons"
|
sort-func="$ctrl.onSort($a, $b)">
|
||||||
draggable="true" droppable="true"
|
{{::item.name}}
|
||||||
acl-role="hr" editable="true">
|
|
||||||
</vn-treeview>
|
</vn-treeview>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</div>
|
<vn-button-bar>
|
||||||
</form>
|
<vn-button ui-sref="worker.index" label="Back"></vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
vn-id="deleteNode"
|
vn-id="deleteNode"
|
||||||
on-response="$ctrl.onRemoveResponse(response)"
|
on-response="$ctrl.onRemoveResponse(response)"
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textfield vn-one
|
<vn-textfield vn-one
|
||||||
label="Name"
|
label="Name"
|
||||||
model="$ctrl.newNode.name">
|
model="$ctrl.newChild.name">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
|
@ -45,4 +44,4 @@
|
||||||
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
|
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
|
||||||
<button response="ACCEPT" translate>Create</button>
|
<button response="ACCEPT" translate>Create</button>
|
||||||
</tpl-buttons>
|
</tpl-buttons>
|
||||||
</vn-dialog>
|
</vn-dialog>
|
||||||
|
|
|
@ -2,31 +2,28 @@ import ngModule from '../module';
|
||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
constructor($scope, $http, vnApp, $translate) {
|
constructor($scope, $http, vnApp, $translate) {
|
||||||
this.$scope = $scope;
|
this.$ = $scope;
|
||||||
this.$http = $http;
|
this.$http = $http;
|
||||||
this.vnApp = vnApp;
|
this.vnApp = vnApp;
|
||||||
this.$translate = $translate;
|
this.$translate = $translate;
|
||||||
this.params = {parentFk: 1};
|
|
||||||
this.icons = [{icon: 'delete', tooltip: 'Delete', callback: this.onDelete}];
|
|
||||||
this.newNode = {
|
|
||||||
name: ''
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreate(parent) {
|
$postLink() {
|
||||||
if (parent instanceof Object)
|
this.$.treeview.fetch();
|
||||||
this.newNode.parentFk = parent.id;
|
|
||||||
|
|
||||||
this.selectedNode = {parent};
|
|
||||||
this.$scope.createNode.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDelete(item, parent, index) {
|
onFetch(item) {
|
||||||
this.selectedNode = {id: item.id, parent, index};
|
const params = item ? {parentId: item.id} : null;
|
||||||
this.$scope.deleteNode.show();
|
return this.$.model.applyFilter({}, params).then(() => {
|
||||||
|
return this.$.model.data;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(item, dragged, dropped) {
|
onSort(a, b) {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* onDrop(item, dragged, dropped) {
|
||||||
if (dropped.scope.item) {
|
if (dropped.scope.item) {
|
||||||
const droppedItem = dropped.scope.item;
|
const droppedItem = dropped.scope.item;
|
||||||
const draggedItem = dragged.scope.item;
|
const draggedItem = dragged.scope.item;
|
||||||
|
@ -38,27 +35,43 @@ class Controller {
|
||||||
|
|
||||||
this.$scope.$apply();
|
this.$scope.$apply();
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
onCreate(parent) {
|
||||||
|
this.newChild = {
|
||||||
|
parent: parent,
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$.createNode.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateDialogOpen() {
|
onCreateDialogOpen() {
|
||||||
this.newNode.name = '';
|
this.newChild.name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateResponse(response) {
|
onCreateResponse(response) {
|
||||||
if (response == 'ACCEPT') {
|
if (response == 'ACCEPT') {
|
||||||
try {
|
try {
|
||||||
if (!this.newNode.name)
|
if (!this.newChild.name)
|
||||||
throw new Error(`Name can't be empty`);
|
throw new Error(`Name can't be empty`);
|
||||||
|
|
||||||
this.$http.post(`/worker/api/Departments/nodeAdd`, this.newNode).then(response => {
|
const params = {name: this.newChild.name};
|
||||||
if (response.data) {
|
const parent = this.newChild.parent;
|
||||||
let parent = this.selectedNode.parent;
|
|
||||||
if ((parent instanceof Object) && !(parent instanceof Array)) {
|
if (parent && parent.id)
|
||||||
const childs = parent.childs;
|
params.parentId = parent.id;
|
||||||
childs.push(response.data);
|
|
||||||
} else if ((parent instanceof Object) && (parent instanceof Array))
|
if (!parent.active)
|
||||||
parent.push(response.data);
|
this.$.treeview.unfold(parent);
|
||||||
}
|
|
||||||
|
const query = `/api/departments/createChild`;
|
||||||
|
this.$http.post(query, params).then(res => {
|
||||||
|
const parent = this.newChild.parent;
|
||||||
|
const item = res.data;
|
||||||
|
item.parent = parent;
|
||||||
|
|
||||||
|
this.$.treeview.create(item);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.vnApp.showError(this.$translate.instant(e.message));
|
this.vnApp.showError(this.$translate.instant(e.message));
|
||||||
|
@ -68,17 +81,17 @@ class Controller {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRemove(item) {
|
||||||
|
this.removedChild = item;
|
||||||
|
this.$.deleteNode.show();
|
||||||
|
}
|
||||||
|
|
||||||
onRemoveResponse(response) {
|
onRemoveResponse(response) {
|
||||||
if (response === 'ACCEPT') {
|
if (response === 'ACCEPT') {
|
||||||
const path = `/worker/api/Departments/nodeDelete`;
|
const childId = this.removedChild.id;
|
||||||
this.$http.post(path, {parentFk: this.selectedNode.id}).then(() => {
|
const path = `/api/departments/${childId}/removeChild`;
|
||||||
let parent = this.selectedNode.parent;
|
this.$http.post(path).then(() => {
|
||||||
|
this.$.treeview.remove(this.removedChild);
|
||||||
if ((parent instanceof Object) && !(parent instanceof Array)) {
|
|
||||||
const childs = parent.childs;
|
|
||||||
childs.splice(this.selectedNode.index, 1);
|
|
||||||
} else if ((parent instanceof Object) && (parent instanceof Array))
|
|
||||||
parent.splice(this.selectedNode.index, 1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue