Improve rendering of tasks with due time

This commit is contained in:
Frederic Guillot
2017-11-08 15:35:21 -08:00
parent 2c7f8ba4b1
commit 99642d96d2
7 changed files with 102 additions and 220 deletions

View File

@@ -4,6 +4,7 @@ namespace Kanboard\Plugin\Calendar\Controller;
use Kanboard\Controller\BaseController; use Kanboard\Controller\BaseController;
use Kanboard\Filter\TaskAssigneeFilter; use Kanboard\Filter\TaskAssigneeFilter;
use Kanboard\Filter\TaskDueDateRangeFilter;
use Kanboard\Filter\TaskProjectFilter; use Kanboard\Filter\TaskProjectFilter;
use Kanboard\Filter\TaskStatusFilter; use Kanboard\Filter\TaskStatusFilter;
use Kanboard\Model\TaskModel; use Kanboard\Model\TaskModel;
@@ -11,17 +12,12 @@ use Kanboard\Model\TaskModel;
/** /**
* Calendar Controller * Calendar Controller
* *
* @package Kanboard\Controller * @package Kanboard\Plugin\Calendar\Controller
* @author Frederic Guillot * @author Frederic Guillot
* @author Timo Litzbarski * @author Timo Litzbarski
*/ */
class CalendarController extends BaseController class CalendarController extends BaseController
{ {
/**
* Show calendar view for a user
*
* @access public
*/
public function user() public function user()
{ {
$user = $this->getUser(); $user = $this->getUser();
@@ -31,11 +27,6 @@ class CalendarController extends BaseController
))); )));
} }
/**
* Show calendar view for a project
*
* @access public
*/
public function project() public function project()
{ {
$project = $this->getProject(); $project = $this->getProject();
@@ -47,66 +38,75 @@ class CalendarController extends BaseController
))); )));
} }
/**
* Get tasks to display on the calendar (project view)
*
* @access public
*/
public function projectEvents() public function projectEvents()
{ {
$project_id = $this->request->getIntegerParam('project_id'); $projectId = $this->request->getIntegerParam('project_id');
$start = $this->request->getStringParam('start'); $startRange = $this->request->getStringParam('start');
$end = $this->request->getStringParam('end'); $endRange = $this->request->getStringParam('end');
$search = $this->userSession->getFilters($project_id); $search = $this->userSession->getFilters($projectId);
$queryBuilder = $this->taskLexer->build($search)->withFilter(new TaskProjectFilter($project_id)); $startColumn = $this->configModel->get('calendar_project_tasks', 'date_started');
$events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end); $dueDateOnlyEvents = $this->taskLexer->build($search)
$events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end)); ->withFilter(new TaskProjectFilter($projectId))
->withFilter(new TaskDueDateRangeFilter(array($startRange, $endRange)))
->format($this->taskCalendarFormatter->setColumns('date_due'));
$startAndDueDateQueryBuilder = $this->taskLexer->build($search)
->withFilter(new TaskProjectFilter($projectId));
$startAndDueDateQueryBuilder
->getQuery()
->addCondition($this->getConditionForTasksWithStartAndDueDate($startRange, $endRange, $startColumn, 'date_due'));
$startAndDueDateEvents = $startAndDueDateQueryBuilder
->format($this->taskCalendarFormatter->setColumns($startColumn, 'date_due'));
$events = array_merge($dueDateOnlyEvents, $startAndDueDateEvents);
$events = $this->hook->merge('controller:calendar:project:events', $events, array( $events = $this->hook->merge('controller:calendar:project:events', $events, array(
'project_id' => $project_id, 'project_id' => $projectId,
'start' => $start, 'start' => $startRange,
'end' => $end, 'end' => $endRange,
)); ));
$this->response->json($events); $this->response->json($events);
} }
/**
* Get tasks to display on the calendar (user view)
*
* @access public
*/
public function userEvents() public function userEvents()
{ {
$user_id = $this->request->getIntegerParam('user_id'); $user_id = $this->request->getIntegerParam('user_id');
$start = $this->request->getStringParam('start'); $startRange = $this->request->getStringParam('start');
$end = $this->request->getStringParam('end'); $endRange = $this->request->getStringParam('end');
$queryBuilder = $this->taskQuery $startColumn = $this->configModel->get('calendar_project_tasks', 'date_started');
$dueDateOnlyEvents = $this->taskQuery
->withFilter(new TaskAssigneeFilter($user_id))
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskDueDateRangeFilter(array($startRange, $endRange)))
->format($this->taskCalendarFormatter->setColumns('date_due'));
$startAndDueDateQueryBuilder = $this->taskQuery
->withFilter(new TaskAssigneeFilter($user_id)) ->withFilter(new TaskAssigneeFilter($user_id))
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)); ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN));
$events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end); $startAndDueDateQueryBuilder
$events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end)); ->getQuery()
->addCondition($this->getConditionForTasksWithStartAndDueDate($startRange, $endRange, $startColumn, 'date_due'));
if ($this->configModel->get('calendar_user_subtasks_time_tracking') == 1) { $startAndDueDateEvents = $startAndDueDateQueryBuilder
$events = array_merge($events, $this->helper->calendar->getSubtaskTimeTrackingEvents($user_id, $start, $end)); ->format($this->taskCalendarFormatter->setColumns($startColumn, 'date_due'));
}
$events = array_merge($dueDateOnlyEvents, $startAndDueDateEvents);
$events = $this->hook->merge('controller:calendar:user:events', $events, array( $events = $this->hook->merge('controller:calendar:user:events', $events, array(
'user_id' => $user_id, 'user_id' => $user_id,
'start' => $start, 'start' => $startRange,
'end' => $end, 'end' => $endRange,
)); ));
$this->response->json($events); $this->response->json($events);
} }
/**
* Update task due date
*
* @access public
*/
public function save() public function save()
{ {
if ($this->request->isAjax() && $this->request->isPost()) { if ($this->request->isAjax() && $this->request->isPost()) {
@@ -118,4 +118,20 @@ class CalendarController extends BaseController
)); ));
} }
} }
protected function getConditionForTasksWithStartAndDueDate($startTime, $endTime, $startColumn, $endColumn)
{
$startTime = strtotime($startTime);
$endTime = strtotime($endTime);
$startColumn = $this->db->escapeIdentifier($startColumn);
$endColumn = $this->db->escapeIdentifier($endColumn);
$conditions = array(
"($startColumn >= '$startTime' AND $startColumn <= '$endTime')",
"($startColumn <= '$startTime' AND $endColumn >= '$startTime')",
"($startColumn <= '$startTime' AND ($endColumn = '0' OR $endColumn IS NULL))",
);
return $startColumn.' IS NOT NULL AND '.$startColumn.' > 0 AND ('.implode(' OR ', $conditions).')';
}
} }

View File

@@ -19,7 +19,6 @@ class ConfigController extends \Kanboard\Controller\ConfigController
public function save() public function save()
{ {
$values = $this->request->getValues(); $values = $this->request->getValues();
$values += array('calendar_user_subtasks_time_tracking' => 0);
if ($this->configModel->save($values)) { if ($this->configModel->save($values)) {
$this->flash->success(t('Settings saved successfully.')); $this->flash->success(t('Settings saved successfully.'));

View File

@@ -1,45 +0,0 @@
<?php
namespace Kanboard\Plugin\Calendar\Formatter;
use Kanboard\Formatter\BaseFormatter;
/**
* Common class to handle calendar events
*
* @package formatter
* @author Frederic Guillot
*/
abstract class BaseTaskCalendarFormatter extends BaseFormatter
{
/**
* Column used for event start date
*
* @access protected
* @var string
*/
protected $startColumn = 'date_started';
/**
* Column used for event end date
*
* @access protected
* @var string
*/
protected $endColumn = 'date_completed';
/**
* Transform results to calendar events
*
* @access public
* @param string $start_column Column name for the start date
* @param string $end_column Column name for the end date
* @return $this
*/
public function setColumns($start_column, $end_column = '')
{
$this->startColumn = $start_column;
$this->endColumn = $end_column ?: $start_column;
return $this;
}
}

View File

@@ -2,33 +2,46 @@
namespace Kanboard\Plugin\Calendar\Formatter; namespace Kanboard\Plugin\Calendar\Formatter;
use DateTime;
use Kanboard\Core\Filter\FormatterInterface; use Kanboard\Core\Filter\FormatterInterface;
use Kanboard\Formatter\BaseFormatter;
/** /**
* Calendar event formatter for task filter * Calendar event formatter for task filter
* *
* @package formatter * @package Kanboard\Plugin\Calendar\Formatter
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class TaskCalendarFormatter extends BaseTaskCalendarFormatter implements FormatterInterface class TaskCalendarFormatter extends BaseFormatter implements FormatterInterface
{ {
/** /**
* Full day event flag * Column used for event start date
* *
* @access private * @access protected
* @var boolean * @var string
*/ */
private $fullDay = false; protected $startColumn = '';
/** /**
* When called calendar events will be full day * Column used for event end date
*
* @access protected
* @var string
*/
protected $endColumn = '';
/**
* Transform results to calendar events
* *
* @access public * @access public
* @return FormatterInterface * @param string $start_column Column name for the start date
* @param string $end_column Column name for the end date
* @return $this
*/ */
public function setFullDay() public function setColumns($start_column, $end_column = '')
{ {
$this->fullDay = true; $this->startColumn = $start_column;
$this->endColumn = $end_column ?: $start_column;
return $this; return $this;
} }
@@ -43,6 +56,17 @@ class TaskCalendarFormatter extends BaseTaskCalendarFormatter implements Formatt
$events = array(); $events = array();
foreach ($this->query->findAll() as $task) { foreach ($this->query->findAll() as $task) {
$startDate = new DateTime();
$startDate->setTimestamp($task[$this->startColumn]);
$endDate = new DateTime();
if (! empty($task[$this->endColumn])) {
$endDate->setTimestamp($task[$this->endColumn]);
}
$allDay = $startDate == $endDate && $endDate->format('Hi') == '0000';
$format = $allDay ? 'Y-m-d' : 'Y-m-d\TH:i:s';
$events[] = array( $events[] = array(
'timezoneParam' => $this->timezoneModel->getCurrentTimezone(), 'timezoneParam' => $this->timezoneModel->getCurrentTimezone(),
'id' => $task['id'], 'id' => $task['id'],
@@ -51,24 +75,13 @@ class TaskCalendarFormatter extends BaseTaskCalendarFormatter implements Formatt
'borderColor' => $this->colorModel->getBorderColor($task['color_id']), 'borderColor' => $this->colorModel->getBorderColor($task['color_id']),
'textColor' => 'black', 'textColor' => 'black',
'url' => $this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'url' => $this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
'start' => date($this->getDateTimeFormat(), $task[$this->startColumn]), 'start' => $startDate->format($format),
'end' => date($this->getDateTimeFormat(), $task[$this->endColumn] ?: time()), 'end' => $endDate->format($format),
'editable' => $this->fullDay, 'editable' => $allDay,
'allday' => $this->fullDay, 'allday' => $allDay,
); );
} }
return $events; return $events;
} }
/**
* Get DateTime format for event
*
* @access private
* @return string
*/
private function getDateTimeFormat()
{
return $this->fullDay ? 'Y-m-d' : 'Y-m-d\TH:i:s';
}
} }

View File

@@ -3,15 +3,12 @@
namespace Kanboard\Plugin\Calendar\Helper; namespace Kanboard\Plugin\Calendar\Helper;
use Kanboard\Core\Base; use Kanboard\Core\Base;
use Kanboard\Core\Filter\QueryBuilder;
use Kanboard\Filter\TaskDueDateRangeFilter;
/** /**
* Calendar Helper * Calendar Helper
* *
* @package helper * @package Kanboard\Plugin\Calendar\Helper
* @author Frederic Guillot * @author Frederic Guillot
* @property \Kanboard\Plugin\Calendar\Formatter\TaskCalendarFormatter $taskCalendarFormatter
*/ */
class CalendarHelper extends Base class CalendarHelper extends Base
{ {
@@ -31,97 +28,4 @@ class CalendarHelper extends Base
return '<div class="js-calendar" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>'; return '<div class="js-calendar" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>';
} }
/**
* Get formatted calendar task due events
*
* @access public
* @param QueryBuilder $queryBuilder
* @param string $start
* @param string $end
* @return array
*/
public function getTaskDateDueEvents(QueryBuilder $queryBuilder, $start, $end)
{
$formatter = $this->taskCalendarFormatter;
$formatter->setFullDay();
$formatter->setColumns('date_due');
return $queryBuilder
->withFilter(new TaskDueDateRangeFilter(array($start, $end)))
->format($formatter);
}
/**
* Get formatted calendar task events
*
* @access public
* @param QueryBuilder $queryBuilder
* @param string $start
* @param string $end
* @return array
*/
public function getTaskEvents(QueryBuilder $queryBuilder, $start, $end)
{
$startColumn = $this->configModel->get('calendar_project_tasks', 'date_started');
$queryBuilder->getQuery()->addCondition($this->getCalendarCondition(
$this->dateParser->getTimestampFromIsoFormat($start),
$this->dateParser->getTimestampFromIsoFormat($end),
$startColumn,
'date_due'
));
$formatter = $this->taskCalendarFormatter;
$formatter->setColumns($startColumn, 'date_due');
return $queryBuilder->format($formatter);
}
/**
* Get formatted calendar subtask time tracking events
*
* @access public
* @param integer $user_id
* @param string $start
* @param string $end
* @return array
*/
public function getSubtaskTimeTrackingEvents($user_id, $start, $end)
{
return $this->subtaskTimeTrackingCalendarFormatter
->withQuery($this->subtaskTimeTrackingModel->getUserQuery($user_id)
->addCondition($this->getCalendarCondition(
$this->dateParser->getTimestampFromIsoFormat($start),
$this->dateParser->getTimestampFromIsoFormat($end),
'start',
'end'
))
)
->format();
}
/**
* Build SQL condition for a given time range
*
* @access public
* @param string $start_time Start timestamp
* @param string $end_time End timestamp
* @param string $start_column Start column name
* @param string $end_column End column name
* @return string
*/
public function getCalendarCondition($start_time, $end_time, $start_column, $end_column)
{
$start_column = $this->db->escapeIdentifier($start_column);
$end_column = $this->db->escapeIdentifier($end_column);
$conditions = array(
"($start_column >= '$start_time' AND $start_column <= '$end_time')",
"($start_column <= '$start_time' AND $end_column >= '$start_time')",
"($start_column <= '$start_time' AND ($end_column = '0' OR $end_column IS NULL))",
);
return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')';
}
} }

View File

@@ -55,7 +55,7 @@ class Plugin extends Base
public function getPluginVersion() public function getPluginVersion()
{ {
return '1.0.1'; return '1.1.0';
} }
public function getPluginHomepage() public function getPluginHomepage()

View File

@@ -25,11 +25,6 @@
) ?> ) ?>
</fieldset> </fieldset>
<fieldset>
<legend><?= t('Subtasks time tracking') ?></legend>
<?= $this->form->checkbox('calendar_user_subtasks_time_tracking', t('Show subtasks based on the time tracking'), 1, $values['calendar_user_subtasks_time_tracking'] == 1) ?>
</fieldset>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button> <button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
</div> </div>