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].
|
|