Add a 'due date' field and display the number of comments on the board

This commit is contained in:
Frédéric Guillot
2014-03-05 22:27:48 -05:00
parent 1e994f3448
commit 544f992424
15 changed files with 195 additions and 6 deletions

View File

@@ -210,6 +210,10 @@ textarea.form-error {
margin-right: 15px; margin-right: 15px;
} }
input.form-date {
width: 150px;
}
/* alerts */ /* alerts */
.alert { .alert {
padding: 8px 35px 8px 14px; padding: 8px 35px 8px 14px;
@@ -462,6 +466,21 @@ nav .active a {
font-style: italic; font-style: italic;
} }
.task-date {
font-weight: bold;
color: #D90000;
}
.task-comment-counter {
position: absolute;
bottom: 0;
right: 5px;
}
.task-footer {
margin-top: 10px;
}
.task { .task {
border: 1px solid #000; border: 1px solid #000;
padding: 5px; padding: 5px;

View File

@@ -216,6 +216,10 @@ class Task extends Base
if (! $task) $this->notfound(); if (! $task) $this->notfound();
$this->checkProjectPermissions($task['project_id']); $this->checkProjectPermissions($task['project_id']);
if (! empty($task['date_due'])) {
$task['date_due'] = date(t('m/d/Y'), $task['date_due']);
}
$this->response->html($this->template->layout('task_edit', array( $this->response->html($this->template->layout('task_edit', array(
'errors' => array(), 'errors' => array(),
'values' => $task, 'values' => $task,

View File

@@ -151,7 +151,7 @@ return array(
'Task opened successfully.' => 'Tâche ouverte avec succès.', 'Task opened successfully.' => 'Tâche ouverte avec succès.',
'Unable to close this task.' => 'Impossible de fermer cette tâche.', 'Unable to close this task.' => 'Impossible de fermer cette tâche.',
'Task closed successfully.' => 'Tâche fermé avec succès.', 'Task closed successfully.' => 'Tâche fermé avec succès.',
'Unable to update your task.' => 'Impossible de fermer cette tâche.', 'Unable to update your task.' => 'Impossible de modifier cette tâche.',
'Task updated successfully.' => 'Tâche mise à jour avec succès.', 'Task updated successfully.' => 'Tâche mise à jour avec succès.',
'Unable to create your task.' => 'Impossible de créer cette tâche.', 'Unable to create your task.' => 'Impossible de créer cette tâche.',
'Task created successfully.' => 'Tâche créée avec succès.', 'Task created successfully.' => 'Tâche créée avec succès.',
@@ -212,4 +212,10 @@ return array(
'Unable to create your comment.' => 'Impossible de sauvegarder votre commentaire.', 'Unable to create your comment.' => 'Impossible de sauvegarder votre commentaire.',
'The description is required' => 'La description est obligatoire', 'The description is required' => 'La description est obligatoire',
'Edit this task' => 'Modifier cette tâche', 'Edit this task' => 'Modifier cette tâche',
'Due Date' => 'Date d\'échéance',
'm/d/Y' => 'd/m/Y', // Date format parsed with php
'month/day/year' => 'jour/mois/année', // Help shown to the user
'Invalid date' => 'Date invalide',
'Must be done before %B %e, %G' => 'Doit être fait avant le %e %B %G',
'%B %e, %G' => '%e %B %G',
); );

View File

@@ -214,5 +214,11 @@ return array(
'Comment added successfully.' => 'Komentarz dodany', 'Comment added successfully.' => 'Komentarz dodany',
'Unable to create your comment.' => 'Nie udało się dodać komentarza', 'Unable to create your comment.' => 'Nie udało się dodać komentarza',
'The description is required' => 'Opis jest wymagany', 'The description is required' => 'Opis jest wymagany',
'Edit this task' => 'Edytuj zadanie' 'Edit this task' => 'Edytuj zadanie',
// 'Due Date' => '',
// 'm/d/Y' => 'd/m/Y', // Date format parsed with php
// 'month/day/year' => 'jour/mois/année', // Help shown to the user
// 'Invalid date' => '',
// 'Must be done before %B %e, %G' => '',
// '%B %e, %G' => '%e %B %G',
); );

View File

@@ -12,13 +12,14 @@ require __DIR__.'/../vendor/SimpleValidator/Validators/Integer.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/Equals.php'; require __DIR__.'/../vendor/SimpleValidator/Validators/Equals.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/AlphaNumeric.php'; require __DIR__.'/../vendor/SimpleValidator/Validators/AlphaNumeric.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/GreaterThan.php'; require __DIR__.'/../vendor/SimpleValidator/Validators/GreaterThan.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/Date.php';
require __DIR__.'/../vendor/PicoDb/Database.php'; require __DIR__.'/../vendor/PicoDb/Database.php';
require __DIR__.'/schema.php'; require __DIR__.'/schema.php';
abstract class Base abstract class Base
{ {
const APP_VERSION = 'master'; const APP_VERSION = 'master';
const DB_VERSION = 8; const DB_VERSION = 9;
private static $dbInstance = null; private static $dbInstance = null;
protected $db; protected $db;
@@ -59,4 +60,19 @@ abstract class Base
return hash('crc32b', $token); return hash('crc32b', $token);
} }
public function getTimestampFromDate($value, $format)
{
$date = \DateTime::createFromFormat($format, $value);
if ($date !== false) {
$errors = \DateTime::getLastErrors();
if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
$timestamp = $date->getTimestamp();
return $timestamp > 0 ? $timestamp : 0;
}
}
return 0;
}
} }

View File

@@ -25,6 +25,14 @@ class Comment extends Base
->findAll(); ->findAll();
} }
public function count($task_id)
{
return $this->db
->table(self::TABLE)
->eq(self::TABLE.'.task_id', $task_id)
->count();
}
public function create(array $values) public function create(array $values)
{ {
$values['date'] = time(); $values['date'] = time();

View File

@@ -2,6 +2,11 @@
namespace Schema; namespace Schema;
function version_9($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_due INTEGER");
}
function version_8($pdo) function version_8($pdo)
{ {
$pdo->exec( $pdo->exec(

View File

@@ -34,6 +34,7 @@ class Task extends Base
self::TABLE.'.description', self::TABLE.'.description',
self::TABLE.'.date_creation', self::TABLE.'.date_creation',
self::TABLE.'.date_completed', self::TABLE.'.date_completed',
self::TABLE.'.date_due',
self::TABLE.'.color_id', self::TABLE.'.color_id',
self::TABLE.'.project_id', self::TABLE.'.project_id',
self::TABLE.'.column_id', self::TABLE.'.column_id',
@@ -66,6 +67,7 @@ class Task extends Base
self::TABLE.'.description', self::TABLE.'.description',
self::TABLE.'.date_creation', self::TABLE.'.date_creation',
self::TABLE.'.date_completed', self::TABLE.'.date_completed',
self::TABLE.'.date_due',
self::TABLE.'.color_id', self::TABLE.'.color_id',
self::TABLE.'.project_id', self::TABLE.'.project_id',
self::TABLE.'.column_id', self::TABLE.'.column_id',
@@ -95,15 +97,23 @@ class Task extends Base
public function getAllByColumnId($project_id, $column_id, $status = array(1)) public function getAllByColumnId($project_id, $column_id, $status = array(1))
{ {
return $this->db $tasks = $this->db
->table(self::TABLE) ->table(self::TABLE)
->columns('tasks.id', 'title', 'color_id', 'project_id', 'owner_id', 'column_id', 'position', 'score', 'users.username') ->columns('tasks.id', 'title', 'color_id', 'project_id', 'owner_id', 'column_id', 'position', 'score', 'date_due', 'users.username')
->join('users', 'id', 'owner_id') ->join('users', 'id', 'owner_id')
->eq('project_id', $project_id) ->eq('project_id', $project_id)
->eq('column_id', $column_id) ->eq('column_id', $column_id)
->in('is_active', $status) ->in('is_active', $status)
->asc('position') ->asc('position')
->findAll(); ->findAll();
$commentModel = new Comment;
foreach ($tasks as &$task) {
$task['nb_comments'] = $commentModel->count($task['id']);
}
return $tasks;
} }
public function countByColumnId($project_id, $column_id, $status = array(1)) public function countByColumnId($project_id, $column_id, $status = array(1))
@@ -122,10 +132,17 @@ class Task extends Base
unset($values['another_task']); unset($values['another_task']);
if (! empty($values['date_due'])) {
$values['date_due'] = $this->getTimestampFromDate($values['date_due'], t('m/d/Y')) ?: null;
}
$values['date_creation'] = time(); $values['date_creation'] = time();
$values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']); $values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']);
$this->db->table(self::TABLE)->save($values); if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
return false;
}
$task_id = $this->db->getConnection()->getLastId(); $task_id = $this->db->getConnection()->getLastId();
@@ -136,9 +153,14 @@ class Task extends Base
public function update(array $values) public function update(array $values)
{ {
if (! empty($values['date_due'])) {
$values['date_due'] = $this->getTimestampFromDate($values['date_due'], t('m/d/Y')) ?: null;
}
return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
} }
// Mark a task closed
public function close($task_id) public function close($task_id)
{ {
return $this->db->table(self::TABLE) return $this->db->table(self::TABLE)
@@ -149,6 +171,7 @@ class Task extends Base
)); ));
} }
// Mark a task open
public function open($task_id) public function open($task_id)
{ {
return $this->db->table(self::TABLE) return $this->db->table(self::TABLE)
@@ -159,11 +182,13 @@ class Task extends Base
)); ));
} }
// Remove a task
public function remove($task_id) public function remove($task_id)
{ {
return $this->db->table(self::TABLE)->eq('id', $task_id)->remove(); return $this->db->table(self::TABLE)->eq('id', $task_id)->remove();
} }
// Move a task to another column or to another position
public function move($task_id, $column_id, $position) public function move($task_id, $column_id, $position)
{ {
return (bool) $this->db return (bool) $this->db
@@ -184,6 +209,7 @@ class Task extends Base
new Validators\Integer('score', t('This value must be an integer')), new Validators\Integer('score', t('This value must be an integer')),
new Validators\Required('title', t('The title is required')), new Validators\Required('title', t('The title is required')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
new Validators\Date('date_due', t('Invalid date'), t('m/d/Y')),
)); ));
return array( return array(
@@ -220,6 +246,7 @@ class Task extends Base
new Validators\Integer('score', t('This value must be an integer')), new Validators\Integer('score', t('This value must be an integer')),
new Validators\Required('title', t('The title is required')), new Validators\Required('title', t('The title is required')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
new Validators\Date('date_due', t('Invalid date'), t('m/d/Y')),
)); ));
return array( return array(

View File

@@ -74,6 +74,19 @@
<?= Helper\escape($task['title']) ?> <?= Helper\escape($task['title']) ?>
</div> </div>
<div class="task-footer">
<?php if (! empty($task['date_due'])): ?>
<div class="task-date">
<?= dt('%B %e, %G', $task['date_due']) ?>
</div>
<?php endif ?>
<?php if (! empty($task['nb_comments'])): ?>
<div class="task-comment-counter">
<?= $task['nb_comments'] ?>
</div>
<?php endif ?>
</div>
</div> </div>
</div> </div>
<?php endforeach ?> <?php endforeach ?>

View File

@@ -40,6 +40,20 @@
<?= Helper\escape($task['title']) ?> <?= Helper\escape($task['title']) ?>
</div> </div>
<div class="task-footer">
<?php if (! empty($task['date_due'])): ?>
<div class="task-date">
<?= dt('%B %e, %G', $task['date_due']) ?>
</div>
<?php endif ?>
<?php if (! empty($task['nb_comments'])): ?>
<div class="task-comment-counter">
<?= $task['nb_comments'] ?>
</div>
<?php endif ?>
</div>
</div> </div>
</div> </div>
<?php endforeach ?> <?php endforeach ?>

View File

@@ -23,6 +23,9 @@
<?= Helper\form_label(t('Story Points'), 'score') ?> <?= Helper\form_label(t('Story Points'), 'score') ?>
<?= Helper\form_number('score', $values, $errors) ?><br/> <?= Helper\form_number('score', $values, $errors) ?><br/>
<?= Helper\form_label(t('Due Date'), 'date_due') ?>
<?= Helper\form_text('date_due', $values, $errors, array('placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/>
<?= Helper\form_label(t('Description'), 'description') ?> <?= Helper\form_label(t('Description'), 'description') ?>
<?= Helper\form_textarea('description', $values, $errors) ?><br/> <?= Helper\form_textarea('description', $values, $errors) ?><br/>
<div class="form-help"><a href="http://en.wikipedia.org/wiki/Markdown#Example" target="_blank"><?= t('Write your text in Markdown') ?></a></div> <div class="form-help"><a href="http://en.wikipedia.org/wiki/Markdown#Example" target="_blank"><?= t('Write your text in Markdown') ?></a></div>

View File

@@ -23,6 +23,9 @@
<?= Helper\form_label(t('Story Points'), 'score') ?> <?= Helper\form_label(t('Story Points'), 'score') ?>
<?= Helper\form_number('score', $values, $errors) ?><br/> <?= Helper\form_number('score', $values, $errors) ?><br/>
<?= Helper\form_label(t('Due Date'), 'date_due') ?>
<?= Helper\form_text('date_due', $values, $errors, array('placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/>
<?= Helper\form_label(t('Description'), 'description') ?> <?= Helper\form_label(t('Description'), 'description') ?>
<?= Helper\form_textarea('description', $values, $errors) ?><br/> <?= Helper\form_textarea('description', $values, $errors) ?><br/>

View File

@@ -20,6 +20,11 @@
<?= dt('Completed on %B %e, %G at %k:%M %p', $task['date_completed']) ?> <?= dt('Completed on %B %e, %G at %k:%M %p', $task['date_completed']) ?>
</li> </li>
<?php endif ?> <?php endif ?>
<?php if ($task['date_due']): ?>
<li>
<strong><?= dt('Must be done before %B %e, %G', $task['date_due']) ?></strong>
</li>
<?php endif ?>
<li> <li>
<strong> <strong>
<?php if ($task['username']): ?> <?php if ($task['username']): ?>

27
tests/TaskTest.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
require_once __DIR__.'/../models/base.php';
require_once __DIR__.'/../models/task.php';
use Model\Task;
class TaskTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
defined('DB_FILENAME') or define('DB_FILENAME', ':memory:');
}
public function testDateFormat()
{
$t = new Task;
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('05/03/2014', 'd/m/Y')));
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('03/05/2014', 'm/d/Y')));
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('3/5/2014', 'm/d/Y')));
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('5/3/2014', 'd/m/Y')));
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('5/3/14', 'd/m/y')));
$this->assertEquals(0, $t->getTimestampFromDate('5/3/14', 'd/m/Y'));
$this->assertEquals(0, $t->getTimestampFromDate('5-3-2014', 'd/m/Y'));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace SimpleValidator\Validators;
use SimpleValidator\Base;
class Date extends Base
{
private $format;
public function __construct($field, $error_message, $format)
{
parent::__construct($field, $error_message);
$this->format = $format;
}
public function execute(array $data)
{
if (isset($data[$this->field]) && $data[$this->field] !== '') {
$date = \DateTime::createFromFormat($this->format, $data[$this->field]);
if ($date !== false) {
$errors = \DateTime::getLastErrors();
return $errors['error_count'] === 0 && $errors['warning_count'] === 0;
}
return false;
}
return true;
}
}