This topic is locked

KANBAN in PHPrunner vs. Mantis BT

11/21/2019 8:12:36 AM
PHPRunner Tips and Tricks
fhumanes author


KANBAN - Management of unplanned tasks
When I have had to manage projects I have used, in a general way, Web2Project/DotProject and Mantis BT. In this way, it managed the planned tasks and those other tasks or actions that arise in the projects (meetings, follow-up, etc.) that are necessary to execute but which, being normally short in execution time, were not included in the planned tasks of the draft.
Mantis BT is a very used and very good product, but so far it does not have something that is now widely used and that is a Kanban board.
This example is a sketch of how we could make an application in PHPRunner that was that substitute for Mantis BT and also had a Kanban interface for the management and monitoring of tasks.
Technically, this example is a PHPrunner application, more or less simple, to which 2 GITHUB javascript libraries have been added.


The interface of the example has been as:


The Kanban board can be viewed from the point of view of the Tasks of the connected user (direct option from the menu) or the Tasks of a Project, which will be requested from the consultation of the Projects.


The result is:


It is a single code for the two visions of the board.
Referenced points indicate:
(1) .- The kanban title gives you information about the vision of the board.
(2) .- By clicking on the "hamburger" we can move from one band (state) to another.
(3) .- The Task information can be customized. In this case, the information of Id, task name, project code, start request date and time estimate to execute the task, user to whom the task has been assigned and task status are used using traffic light code ( risk control).

In this area you can click to open a popup window with the Task information.
(4) .- The states (bands) are configurable and their color is also defined.
(5) .- When a Task is changed from one state to another, an asynchronous request for updating the Database is produced and I use the Notification management to report the fact and its result.
You can test the result at https://fhumanes.com/runner2kanban/, with the login "admin" and password "admin".
The most interesting codes are:

  • Code to represent the board "kanban.php".



<?php

$url = $_SESSION['config'][array_search('URL', array_column($_SESSION['config'], 'name'))][value];

$kanban_path = $_SESSION['config'][array_search('KANBAN_PATH', array_column($_SESSION['config'], 'name'))][value];

$kanban_icon = $_SESSION['config'][array_search('KANBAN_ICON', array_column($_SESSION['config'], 'name'))][value];

$kanban_ajax = $_SESSION['config'][array_search('KANBAN_AJAX', array_column($_SESSION['config'], 'name'))][value];

$notify = $_SESSION['config'][array_search('NOTIFY', array_column($_SESSION['config'], 'name'))][value];

$language = $_SESSION['language'];

$kanban_option = $_SESSION['kanban_option'];

// Load lanes

global $conn;

$sql="SELECT `idlanes`, `name`, `color`, `orden`, `is_end` FROM lanes ORDER BY `orden`";

$resql=db_query($sql,$conn);

$data_lane=$resql->fetch_all(MYSQLI_ASSOC);

// load Task

If ($kanban_option == 'project') {

$where = "t.`projects_project_id` =".$_SESSION['project_id'];

} else {

$where = "t.`owner` = ".$_SESSION['user_id'];

}

$sql="

SELECT

t.`task_id`,

p.`short_name`,

t.`name`,

t.`start_date`,

t.`duration`,

catalog_code,

t.`status`,

t.`situation`,

u.`login`

FROM tasks t

join projects p on (t.`projects_project_id` = p.`project_id`)

join ( SELECT c.catalog_num, c.catalog_code FROM `catalog` AS c INNER JOIN super_catalog AS s ON c.super_catalog_idsuper_catalog = s.idsuper_catalog

where super_code = 'DURATION_TYPE' AND LANGUAGE = '$language' ) d on (t.`duration_type` = d.catalog_num)

join users u on (t.`owner` = u.`user_id`)

where $where

order by t.`status`, t.`start_date`, t.`task_id` ";

$resql=db_query($sql,$conn);

$data_task=$resql->fetch_all(MYSQLI_ASSOC);

$style_lane = '';

foreach ($data_lane as $lane) {

// .success {background: #00b961;}

$style_lane .= ".".$lane[name]."{ background:".$lane[color]."}";

}

$display = "

<link rel=\"stylesheet\" href=\"$kanban_path/jkanban.css\" />

<style>

#myKanban {

overflow-x: auto;

padding: 5px 0; /* 10px */

}";

$display .= $style_lane;

$display .=

" </style>

</head>

<body>

<div id=\"myKanban\"></div>

<script src=\"$kanban_path/jkanban.js\"></script>

<script src=\"$notify/notify.js\"></script>

<script>

var HttpClient = function() {

this.get = function(aUrl, aCallback) {

var anHttpRequest = new XMLHttpRequest();

anHttpRequest.onreadystatechange = function() {

if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200)

aCallback(anHttpRequest.responseText);

}

anHttpRequest.open( \"GET\", aUrl, true );

anHttpRequest.send( null );

}

}

// -----------------------------------------------------------------------------------------------------

var site ='$kanban_ajax?'; // Para informar del cambio de Lane

var lane = ''; // Variable para saber la Lane que recibe el objeto

var task = ''; // Variable para saber la tarea que se cambia



// -----------------------------------------------------------------------------------------------------

function onShowNotification () {

// console.log('notification is shown!');

}

function onCloseNotification () {

// console.log('notification is closed!');

}

function onClickNotification () {

// console.log('notification was clicked!');

}

function onErrorNotification () {

console.error('Error showing notification. You may need to request permission.');

}

function onPermissionGranted () {

console.log('Permission has been granted by the user');

doNotification();

}

function onPermissionDenied () {

console.warn('Permission has been denied by the user');

}

function doNotification (response='') {

if (!Notify.needsPermission) {

var myNotification = new Notify('PHPRuner KANBAN', {

body: 'La tarea ('+task+') ha cambiado al estado: '+lane+' Respuesta: '+response,

tag: task,

notifyShow: onShowNotification,

notifyClose: onCloseNotification,

notifyClick: onClickNotification,

notifyError: onErrorNotification,

timeout: 6

});

myNotification.show();

} else if (Notify.isSupported()) {

Notify.requestPermission(onPermissionGranted, onPermissionDenied);

}

}

// -----------------------------------------------------------------------------------------------------

var KanbanTest = new jKanban({

element: \"#myKanban\",

gutter: \"10px\",

widthBoard: \"350px\",

itemHandleOptions:{

enabled: true,

},

click: function(el) {

console.log(\"Trigger on all items click!\");

console.log(el);

},

dropEl: function(el, target, source, sibling){

console.log('dropEL');

console.log(target.parentElement.getAttribute('data-id'));

lane = target.parentElement.getAttribute('data-id');

console.log(el, target, source, sibling)

},

buttonClick: function(el, boardId) {

console.log('buttonClick');

console.log(el);

console.log(boardId);

// create a form to enter element

var formItem = document.createElement(\"form\");

formItem.setAttribute(\"class\", \"itemform\");

formItem.innerHTML =

'<div class=\"form-group\"><textarea class=\"form-control\" rows=\"2\" autofocus></textarea></div><div class=\"form-group\"><button type=\"submit\" class=\"btn btn-primary btn-xs pull-right\">Submit</button><button type=\"button\" id=\"CancelBtn\" class=\"btn btn-default btn-xs pull-right\">Cancel</button></div>';

KanbanTest.addForm(boardId, formItem);

formItem.addEventListener(\"submit\", function(e) {

e.preventDefault();

var text = e.target[0].value;

KanbanTest.addElement(boardId, {

title: text

});

formItem.parentNode.removeChild(formItem);

});

document.getElementById(\"CancelBtn\").onclick = function() {

formItem.parentNode.removeChild(formItem);

};

},

addItemButton: false,

boards: [";

for ($i = 0; $i < count($data_lane); $i++) {

$lane = $data_lane[$i];

$lane_pos = $data_lane[$i+1];

$lane_min = $data_lane[$i-1];

$display .= "{ id: \"".$lane[name]."\", title: \"".$lane[name]."\", class: \"".$lane[name]."\", item: [ \n ";

$display_task = 0;

for ($k = 0; $k < count($data_task); $k++) {

$task = $data_task[$k];

$task[name]= addslashes($task[name]); // save " in Name

if ($task[status] === $lane[idlanes]) {

$display_task = 1;

// Situation = semaphore

$img_situation = '';

switch ($task[situation]) {

// <img src=\\\"$kanban_icon/red.png\\\"> <img src=\\\"$kanban_icon/lock.png\\\">

case 0:

$img_situation = " <img src=\\\"$kanban_icon/green.png\\\">";

break;

case 1:

$img_situation = " <img src=\\\"$kanban_icon/orange.png\\\">";

break;

case 2:

$img_situation = " <img src=\\\"$kanban_icon/red.png\\\">";

break;

case 3:

$img_situation = " <img src=\\\"$kanban_icon/red.png\\\"> <img src=\\\"$kanban_icon/lock.png\\\">";

break;

}

$display .= "{ id: \"".$task[task_id]."\", title: \"".

"<img src=\\\"$kanban_icon/id.png\\\">".$task[task_id]."<img src=\\\"$kanban_icon/title.png\\\">".$task[name].

"<BR><img src=\\\"$kanban_icon/project.png\\\">".$task[short_name]."<img src=\\\"$kanban_icon/date.png\\\">".substr($task[start_date], 0, -8).", ".$task[duration]." ".$task[catalog_code]].

"<BR><img src=\\\"$kanban_icon/user.png\\\">".$task[login].$img_situation."\",\n";

$display .= "

click: function(el) {

task = el.dataset.eid;

var url = \"kanban_view.php?editid1=\"+task;

var header = 'Task: '+ task ;

window.popup = Runner.displayPopup({url : url,

width: 900,

height: 550,

header: header

});

},

drag: function(el, source) {

console.log(\"START DRAG: \" + el.dataset.eid);

},

dragend: function(el) {

task = el.dataset.eid;

console.log(\"END DRAG: \" + task);

console.log(lane);

// Solicitud de actuaización de cambio de tarea

var client = new HttpClient();

client.get(site+'id='+task+'&lane='+lane, function(response) {

console.log('Respuesta: '+response);

doNotification(response);

});

},

drop: function(el) {

console.log(\"DROPPED: \" + el.dataset.eid);

}";

$display .= "\n },";

}

}

if ($display_task === 1 ){

$display_task = 0;

$display = substr($display, 0, -1); // Quitar la última ","

}

$display .= " ] }\n ,";

}

// $display .= "}\n,";

$display = substr($display, 0, -1); // Quitar la última ","

$display .= " ]} ); </script>";

$value = $display;

?>


  • Asynchronous update code "kanban_ajax.php"





<?php

@ini_set("display_errors","1");

@ini_set("display_startup_errors","1");

require_once("include/dbcommon.php");

header("Expires: Thu, 01 Jan 1970 00:00:01 GMT");

$query = $_SERVER['QUERY_STRING'];

$parameters = explode( '&', $query);

$parameter = explode( '=', $parameters[0]);

if ($parameter[0] == 'id') {

$task_id = $parameter[1];

} else {

echo "error parameter(1)!!!!!!". $query;

die();

}

$parameter = explode( '=', $parameters[1]);

if ($parameter[0] == 'lane') {

$lane_name = $parameter[1];

} else {

echo "error parameter(2)!!!!!!" . $query;

die();

}

// Update task for lane

global $conn;

$sql="SELECT `idlanes`, `is_end` FROM lanes where `name` = '$lane_name'";

$resql=db_query($sql,$conn);

if ($row = db_fetch_array($resql)) {

$lane_id = $row[idlanes];

$is_end = $row[is_end];

if ( $is_end == 0 ) {

$sql_2 ="update tasks set `status` = $lane_id, end_date = null where `task_id` = $task_id";

} else {

$sql_2 ="update tasks set `status` = $lane_id, end_date = now() where `task_id` = $task_id";

}

$resql_2=db_query($sql_2,$conn);

echo "OK";

} else {

echo "error parameter(3)!!!!!!" . $lane_name ." not exist";

die();

}

?>


[size="3"]This is not an application. It is a set of ideas and code to develop your solution.[/size]
In my blog I leave the code of the example. https://fhumanes.com/blog/gestor-de-proyectos/kanban/
As always, for any questions or what you need, tell me via email [email="fernandohumanes@gmail.com"]fernandohumanes@gmail.com[/email].

mbintex 11/22/2019

Interesting input - thanks for the suggestions.
I have been playing around with the Kanban idea too, but made it with built-in functionality only.
What is missing here, is the ability to drag&drop items between the columns. I would really like to see some drag&drop functionality in PHPRunner for example for sorting items.

fhumanes author 11/22/2019

Thank you.
From my point of view, it is good that PHPrunner has a solution for many things (the most common) but more important is that it has the possibility of integrating (being expandable) to any javascript or PHP library, which is available on the internet.
In the example I have given, it does what you request and still works in PHPrunner.
Regards,

fhumanes author 11/25/2019

Please,
If by mistake or for anything, when you use the examples I leave you, an error occurs or does not come out what I had to leave, I beg you to indicate it to me to correct it.
If we destroy the examples, there will be colleagues who cannot see whether or not the information provided may be useful.
Thank you,
fernando