diff --git a/includes/t-data/dao_tasks.inc.php b/includes/t-data/dao_tasks.inc.php index 796f0c1..301c922 100644 --- a/includes/t-data/dao_tasks.inc.php +++ b/includes/t-data/dao_tasks.inc.php @@ -98,6 +98,27 @@ class DAO_Tasks . 'INNER JOIN users u USING (user_id) ' . 'WHERE n.task_id = $1 ' . 'ORDER BY n.note_added DESC' )->execute( $id ); + $task->dependencies = $this->query( + 'SELECT t.task_id AS id , t.task_title AS title , t.item_id AS item , ' + . 'i.item_name AS item_name , ' + . '( ct.completed_task_time IS NOT NULL ) AS completed ' + . 'FROM task_dependencies td ' + . 'INNER JOIN tasks t ON t.task_id = td.task_id_depends ' + . 'INNER JOIN items i USING ( item_id ) ' + . 'LEFT OUTER JOIN completed_tasks ct ON ct.task_id = t.task_id ' + . 'WHERE td.task_id = $1 ' + . 'ORDER BY i.item_name , t.task_priority , t.task_title' )->execute( $id ); + $task->reverseDependencies = $this->query( + 'SELECT t.task_id AS id , t.task_title AS title , t.item_id AS item , ' + . 'i.item_name AS item_name , ' + . '( ct.completed_task_time IS NOT NULL ) AS completed ' + . 'FROM task_dependencies td ' + . 'INNER JOIN tasks t USING( task_id ) ' + . 'INNER JOIN items i USING ( item_id ) ' + . 'LEFT OUTER JOIN completed_tasks ct USING ( task_id ) ' + . 'WHERE td.task_id_depends = $1 ' + . 'ORDER BY i.item_name , t.task_priority , t.task_title' )->execute( $id ); + return $task; } @@ -113,6 +134,30 @@ class DAO_Tasks } + public function canFinish( $task ) + { + assert( $task->completed_at == null ); + foreach ( $task->dependencies as $dependency ) { + if ( $dependency->completed != 't' ) { + return false; + } + } + return true; + } + + + public function canRestart( $task ) + { + assert( $task->completed_at != null ); + foreach ( $task->reverseDependencies as $dependency ) { + if ( $dependency->completed == 't' ) { + return false; + } + } + return true; + } + + public function delete( $task ) { $this->query( 'DELETE FROM tasks WHERE task_id = $1' )->execute( $task ); diff --git a/includes/t-tasks/controllers.inc.php b/includes/t-tasks/controllers.inc.php index 12bc91d..4f3108f 100644 --- a/includes/t-tasks/controllers.inc.php +++ b/includes/t-tasks/controllers.inc.php @@ -66,15 +66,20 @@ class Ctrl_TaskDetails $items->getLineage( $this->task->item = $items->get( $this->task->item ) ); $box = Loader::View( 'box' , $bTitle , Loader::View( 'task_details' , $this->task ) ); + + $tasks = Loader::DAO( 'tasks' ); if ( $this->task->completed_by === null ) { $box->addButton( BoxButton::create( 'Edit task' , 'tasks/edit?id=' . $this->task->id ) - ->setClass( 'icon edit' ) ) - ->addButton( BoxButton::create( 'Mark as completed' , - 'tasks/finish?id=' . $this->task->id ) - ->setClass( 'icon stop' ) ); + ->setClass( 'icon edit' ) ); + if ( $tasks->canFinish( $this->task ) ) { + $box->addButton( BoxButton::create( 'Mark as completed' , 'tasks/finish?id=' . $this->task->id ) + ->setClass( 'icon stop' ) ); + }; } else { - $box->addButton( BoxButton::create( 'Re-activate' , 'tasks/restart?id=' . $this->task->id ) + if ( $tasks->canRestart( $this->task ) ) { + $box->addButton( BoxButton::create( 'Re-activate' , 'tasks/restart?id=' . $this->task->id ) ->setClass( 'icon start' ) ); + } $timestamp = strtotime( $this->task->completed_at ); } @@ -88,6 +93,34 @@ class Ctrl_TaskDetails } +class Ctrl_TaskDependencies + extends Controller +{ + private $task; + + public function __construct( $task ) + { + $this->task = $task; + } + + + public function handle( Page $page ) + { + $views = array( + Loader::View( 'box' , 'Dependencies' , + Loader::View( 'task_dependencies' , $this->task , false ) ) + ); + + if ( ! empty( $this->task->reverseDependencies ) ) { + array_push( $views , Loader::View( 'box' , 'Reverse dependencies' , + Loader::View( 'task_dependencies' , $this->task , true ) ) ); + } + + return $views; + } +} + + class Ctrl_TaskNotes extends Controller { @@ -161,9 +194,9 @@ class Ctrl_ToggleTask return 'tasks'; } - if ( $this->isRestart ) { + if ( $this->isRestart && $tasks->canRestart( $task ) ) { $tasks->restart( $id , '[AUTO] Task re-activated.' ); - } else { + } else if ( ! $this->isRestart && $tasks->canFinish( $task ) ) { $tasks->finish( $id , '[AUTO] Task completed.' ); } diff --git a/includes/t-tasks/package.inc.php b/includes/t-tasks/package.inc.php index b0092d8..7c63925 100644 --- a/includes/t-tasks/package.inc.php +++ b/includes/t-tasks/package.inc.php @@ -22,6 +22,7 @@ $package[ 'ctrls' ][] = 'edit_note_form'; $package[ 'ctrls' ][] = 'edit_note'; $package[ 'ctrls' ][] = 'edit_task_form'; $package[ 'ctrls' ][] = 'edit_task'; +$package[ 'ctrls' ][] = 'task_dependencies'; $package[ 'ctrls' ][] = 'task_details'; $package[ 'ctrls' ][] = 'task_notes'; $package[ 'ctrls' ][] = 'toggle_task'; @@ -31,5 +32,6 @@ $package[ 'views' ][] = 'all_tasks'; $package[ 'views' ][] = 'tasks'; $package[ 'views' ][] = 'task_details'; $package[ 'views' ][] = 'task_note'; +$package[ 'views' ][] = 'task_dependencies'; $package[ 'pages' ][] = 'tasks_tasks'; diff --git a/includes/t-tasks/page_controllers.inc.php b/includes/t-tasks/page_controllers.inc.php index 858f03a..a2e9495 100644 --- a/includes/t-tasks/page_controllers.inc.php +++ b/includes/t-tasks/page_controllers.inc.php @@ -150,7 +150,10 @@ class Ctrl_ViewTask } $page->setTitle( $task->title . ' (task)' ); - $result = array( Loader::Ctrl( 'task_details' , $task ) ); + $result = array( + Loader::Ctrl( 'task_details' , $task ) , + Loader::Ctrl( 'task_dependencies' , $task ) , + ); if ( $task->completed_by === null ) { array_push( $result , Loader::Ctrl( 'add_task_note_form' , $task ) ); diff --git a/includes/t-tasks/views.inc.php b/includes/t-tasks/views.inc.php index 09fabea..c81ccff 100644 --- a/includes/t-tasks/views.inc.php +++ b/includes/t-tasks/views.inc.php @@ -272,7 +272,7 @@ class View_TaskNote class View_TaskDependencies - implements View + extends BaseURLAwareView { private $task; private $reverse; @@ -286,16 +286,35 @@ class View_TaskDependencies public function render( ) { $source = $this->reverse ? 'reverseDependencies' : 'dependencies'; + if ( empty( $this->task->$source ) ) { + return HTML::make( 'div' ) + ->setAttribute( 'class' , 'no-table' ) + ->appendText( 'This task has no dependencies.' ); + } + $list = HTML::make( 'ul' )->setAttribute( 'class' , 'dep-list' ); + $prevItem = null; + $itemList = null; foreach ( $this->task->$source as $dependency ) { + if ( $prevItem !== $dependency->item ) { + $itemList = HTML::make( 'ul' ); + $list->appendElement( HTML::make( 'li' ) + ->appendText( 'In ' ) + ->appendElement( HTML::make( 'a' ) + ->setAttribute( 'href' , $this->base . '/items/view?id=' . $dependency->item ) + ->appendText( $dependency->item_name ) ) + ->appendElement( $itemList ) ); + $prevItem = $dependency->item; + } $link = HTML::make( 'a' ) - ->setAttribute( 'href' , 'tasks/view?id=' . $dependency->id ) + ->setAttribute( 'href' , $this->base . '/tasks/view?id=' . $dependency->id ) ->appendText( $dependency->title ); if ( ! $this->reverse ) { $link->setAttribute( 'class' , ( $dependency->completed == 't' ) ? 'satisfied' : 'missing' ); } - $list->appendElement( HTML::make( 'li' )->appendElement( $link ) ); + $itemList->appendElement( HTML::make( 'li' )->appendElement( $link ) ); } + return $list; } } diff --git a/site/style.css b/site/style.css index 7f17d98..e5dc4f6 100644 --- a/site/style.css +++ b/site/style.css @@ -393,3 +393,11 @@ dl.tasks dt.sub-title.completed { color: #bfbfbf; text-shadow: 1px 1px 2px #3f3f3f; } + +ul.dep-list a.missing { + color: red; +} + +ul.dep-list a.satisfied { + color: green; +}