From 5e7565d8dfbd83589ccd152a13cae0bf5240ceff Mon Sep 17 00:00:00 2001 From: Drake Date: Wed, 6 Sep 2006 22:37:00 +0000 Subject: [PATCH] Major enhancement: add multiple-resource support for tasks in the project management asset. This should still integrate with the time tracker properly, and the data migration across the schema change should be handled by the upgrade script. --- docs/changelog/7.x.x.txt | 1 + .../project_manager_edit_task.tmpl | 73 ++ .../project_manager_project_display.tmpl | 669 ++++++++++++++++++ .../project_manager_resource_list.tmpl | 21 + .../project_manager_resource_popup.tmpl | 42 ++ docs/upgrades/upgrade_7.0.6-7.0.7.pl | 36 +- lib/WebGUI/Asset/Wobject/ProjectManager.pm | 317 ++++++++- .../i18n/English/Asset_ProjectManager.pm | 79 +++ www/extras/wobject/ProjectManager/cMenu.js | 2 +- www/extras/wobject/ProjectManager/groups.gif | Bin 0 -> 680 bytes .../wobject/ProjectManager/taskEdit.css | 30 + www/extras/wobject/ProjectManager/taskEdit.js | 111 +++ www/extras/wobject/ProjectManager/users.gif | Bin 0 -> 680 bytes 13 files changed, 1345 insertions(+), 36 deletions(-) create mode 100644 docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl create mode 100644 docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl create mode 100644 docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl create mode 100644 docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl create mode 100644 www/extras/wobject/ProjectManager/groups.gif create mode 100644 www/extras/wobject/ProjectManager/taskEdit.css create mode 100644 www/extras/wobject/ProjectManager/taskEdit.js create mode 100644 www/extras/wobject/ProjectManager/users.gif diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index ed845bed8..1e4aa4c46 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -44,6 +44,7 @@ - fix: Matrix (updated detailed listing template to include the screenshot) and fixed a bug in sbin/fileUpload.pl wher it didn't handle images with uppercased extensions properly (Martin Kamerbeek / Procolix) + - new: In the Project Management asset, tasks can now have multiple resources, which may be users or groups. Original single-resource data is migrated to the new schema by the 7.0.7 upgrade script. 7.0.6 - fix: Error in DateTime.pm diff --git a/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl b/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl new file mode 100644 index 000000000..91f4ddb0d --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl @@ -0,0 +1,73 @@ +#ProjectManagerTMPL0004 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl b/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl new file mode 100644 index 000000000..2025a7fd1 --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl @@ -0,0 +1,669 @@ +#ProjectManagerTMPL0002 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Task NameDurationStartFinishPred +
+ +
+
 
+ + + + + + +
 
 
+ + + + +~~~ + + + + diff --git a/docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl b/docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl new file mode 100644 index 000000000..41ed62768 --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl @@ -0,0 +1,21 @@ +#ProjectManagerTMPL0006 +#create +#namespace:ProjectManager_resourceList +#url:default-pm-resource-list +#title:Default Resource List +#menuTitle:Default Resource List + + + + class="odd"> + + + + + +
+ + + <tmpl_var opTitle> +
diff --git a/docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl b/docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl new file mode 100644 index 000000000..b26dda6b2 --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl @@ -0,0 +1,42 @@ +#ProjectManagerTMPL0005 +#create +#namespace:ProjectManager_resourcePopup +#url:default-pm-resource-popup +#title:Default Resource Popup +#menuTitle:Default Resource Popup + + + +<tmpl_var title> + + + + + +
+ + + + + + +

+

+ diff --git a/docs/upgrades/upgrade_7.0.6-7.0.7.pl b/docs/upgrades/upgrade_7.0.6-7.0.7.pl index 3666b635d..f5f8e0fdb 100644 --- a/docs/upgrades/upgrade_7.0.6-7.0.7.pl +++ b/docs/upgrades/upgrade_7.0.6-7.0.7.pl @@ -22,6 +22,7 @@ my $session = start(); # this line required # upgrade functions go here dropLineageInAssetIndex($session); +giveTasksMultipleResources($session); finish($session); # this line required @@ -34,7 +35,40 @@ sub dropLineageInAssetIndex { $session->db->write('alter table assetIndex drop column lineage'); } - +sub giveTasksMultipleResources { + my $session = shift; + print "\tMaking tasks handle multiple resources.\n" unless $quiet; + $session->db->write($_) for(<<'EOT', + CREATE TABLE PM_taskResource ( + taskResourceId varchar(22) character set utf8 collate utf8_bin NOT NULL, + taskId varchar(22) character set utf8 collate utf8_bin NOT NULL, + sequenceNumber int(11) NOT NULL, + resourceKind enum('user', 'group') NOT NULL, + resourceId varchar(22) character set utf8 collate utf8_bin NOT NULL, + UNIQUE (taskId, resourceKind, resourceId), + UNIQUE (taskId, sequenceNumber), + PRIMARY KEY (taskResourceId) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +EOT + <<'EOT', + INSERT INTO PM_taskResource + (taskResourceId, taskId, sequenceNumber, resourceKind, resourceId) + SELECT taskId, taskId, 1, 'user', resourceId + FROM PM_task WHERE resourceId IS NOT NULL; +EOT + <<'EOT', + ALTER TABLE PM_task + DROP COLUMN resourceId; +EOT + <<'EOT', + ALTER TABLE PM_wobject + ADD COLUMN resourcePopupTemplateId varchar(22) character set utf8 collate utf8_bin NOT NULL + DEFAULT 'ProjectManagerTMPL0005', + ADD COLUMN resourceListTemplateId varchar(22) character set utf8 collate utf8_bin NOT NULL + DEFAULT 'ProjectManagerTMPL0006' +EOT + ); +} # ---- DO NOT EDIT BELOW THIS LINE ---- diff --git a/lib/WebGUI/Asset/Wobject/ProjectManager.pm b/lib/WebGUI/Asset/Wobject/ProjectManager.pm index c2c964a54..d1bfb4d74 100644 --- a/lib/WebGUI/Asset/Wobject/ProjectManager.pm +++ b/lib/WebGUI/Asset/Wobject/ProjectManager.pm @@ -17,6 +17,7 @@ use DateTime; use Tie::IxHash; use WebGUI::International; use WebGUI::Utility; +use WebGUI::HTML; use POSIX qw(ceil floor); use base 'WebGUI::Asset::Wobject'; @@ -115,6 +116,22 @@ sub definition { hoverHelp=>$i18n->get('editTaskTemplate hoverhelp'), label=>$i18n->get('editTaskTemplate label') }, + resourcePopupTemplateId =>{ + fieldType=>"template", + defaultValue=>'ProjectManagerTMPL0005', + tab=>"display", + namespace=>"ProjectManager_resourcePopup", + hoverHelp=>$i18n->get('resourcePopupTemplate hoverhelp'), + label=>$i18n->get('resourcePopupTemplate label') + }, + resourceListTemplateId =>{ + fieldType=>"template", + defaultValue=>'ProjectManagerTMPL0006', + tab=>"display", + namespace=>"ProjectManager_resourceList", + hoverHelp=>$i18n->get('resourceListTemplate hoverhelp'), + label=>$i18n->get('resourceListTemplate label') + }, groupToAdd => { fieldType=>"group", defaultValue=>3, @@ -160,20 +177,42 @@ sub getProjectInstance { #------------------------------------------------------------------- #API method called by Time Tracker to return all projects in all assets for which the user passed in has tasks assigned sub getProjectList { - my $self = shift; - my $db = $self->session->db; - my $userId = $_[0]; - return $db->buildHashRef("select a.projectId,a.name from PM_project a, PM_task b where a.projectId=b.projectId and b.resourceId=?",[$userId]); + my $self = shift; + my $db = $self->session->db; + my $userId = $_[0]; + my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups}; + my $groupIdQuery = @groupIds? + ('PM_taskResource.resourceId IN ('.join(',', map{'?'} @groupIds).')') : '0'; + + $self->session->db->buildHashRef(<<"SQL", [$userId, @groupIds]); +SELECT DISTINCT PM_project.projectId, PM_project.name + FROM PM_project + INNER JOIN PM_task ON PM_project.projectId = PM_task.projectId + INNER JOIN PM_taskResource ON PM_task.taskId = PM_taskResource.taskId + WHERE (PM_taskResource.resourceKind = 'user' AND PM_taskResource.resourceId = ?) + OR (PM_taskResource.resourceKind = 'group' AND $groupIdQuery) +SQL } #------------------------------------------------------------------- #API method called by Time Tracker to return all tasks for the projectId passed in sub getTaskList { - my $self = shift; - my $db = $self->session->db; - my $projectId = $_[0]; - my $userId = $_[1]; - return $db->buildHashRef("select taskId, taskName from PM_task where projectId=? and resourceId=?",[$projectId,$userId]); + my $self = shift; + my $db = $self->session->db; + my $projectId = $_[0]; + my $userId = $_[1]; + my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups}; + my $groupIdQuery = @groupIds? + ('PM_taskResource.resourceId IN ('.join(',', map{'?'} @groupIds).')') : '0'; + + $self->session->db->buildHashRef(<<"SQL", [$projectId, $userId, @groupIds]); +SELECT DISTINCT PM_task.taskId, PM_task.taskName + FROM PM_task + INNER JOIN PM_taskResource ON PM_task.taskId = PM_taskResource.taskId + WHERE PM_task.projectId = ? + AND ((PM_taskResource.resourceKind = 'user' AND PM_taskResource.resourceId = ?) + OR (PM_taskResource.resourceKind = 'group' AND $groupIdQuery)) +SQL } #------------------------------------------------------------------- @@ -566,6 +605,175 @@ sub www_editProjectSave { } #------------------------------------------------------------------- +sub _htmlOfResourceList { + my $self = shift; + my %args = %{+shift}; + my @resources = @_; + my @listItems; + my $assetExtras = $self->session->url->extras('wobject/ProjectManager'); + my $var = {}; + + $var->{assetExtras} = $assetExtras; + $var->{resourceLoop} = []; + + my $lastOdd = 0; + foreach my $row (@resources) { + my $subvar = {}; + my ($resourceKind, $resourceId) = @$row{qw{resourceKind resourceId}}; + my $odd = ($lastOdd = !$lastOdd); + + $subvar->{resourceKind} = $resourceKind; + $subvar->{resourceId} = $resourceId; + $subvar->{opCallbackJs} = $args{opCallbackJs}; + $subvar->{opIcon} = $args{opIcon}; + $subvar->{opTitle} = $args{opTitle}; + $subvar->{assetExtras} = $assetExtras; + $subvar->{odd} = $odd; + $subvar->{hiddenFields} = $args{hiddenFields}; + + if ($resourceKind eq 'group') { + my $group = WebGUI::Group->new($self->session, $resourceId); + $subvar->{resourceName} = WebGUI::HTML::format($group->name, 'text'); + $subvar->{resourceIcon} = 'groups.gif'; + } elsif ($resourceKind eq 'user') { + my $user = WebGUI::User->new($self->session, $resourceId); + $subvar->{resourceName} = WebGUI::HTML::format($user->profileField('lastName').', '.$user->profileField('firstName'), 'text'); + $subvar->{resourceIcon} = 'users.gif'; + } else { + $self->session->errorHandler->fatal("Unknown kind of resource '$resourceKind'!"); + } + + push @{$var->{resourceLoop}}, $subvar; + } + + return $self->processTemplate($var, $self->getValue('resourceListTemplateId')); +} + +sub _resourceSearchPopup { + my $self = shift; + my %args = @_; + my $i18n = WebGUI::International->new($self->session,'Asset_ProjectManager'); + + my $doSearch = $self->session->form->param('doSearch'); + my $jsCallback = $self->session->form->param('callback'); + $jsCallback =~ tr/A-Za-z0-9_//cd; + my $selfUrlHtml = WebGUI::HTML::format($self->getUrl, 'text'); + my $assetExtras = $self->session->url->extras('wobject/ProjectManager'); + + my ($search, $exclude) = map {scalar $self->session->form->param($_)} ('search', 'exclude'); + my ($searchHtml, $excludeHtml) = map {WebGUI::HTML::format($_, 'text')} ($search, $exclude); + my $var = {}; + + my $i18nprefix = $args{i18nprefix}; + foreach my $key (qw/title searchText foundMessage notFoundMessage/) { + $var->{$key} = $i18n->get("$i18nprefix $key"); + } + + $var->{assetExtras} = $assetExtras; + $var->{func} = $args{func}; + $var->{callback} = $jsCallback; + $var->{exclude} = $excludeHtml; + $var->{previousSearch} = $searchHtml; + $var->{selfUrl} = $selfUrlHtml; + + if ($doSearch) { + my @resources = @{$self->session->db->buildArrayRefOfHashRefs($args{queryCallback}->($exclude, $search))}; + $var->{doingSearch} = 1; + $var->{foundResults} = scalar @resources; + + $var->{resourceDiv} = '
'.$self->_htmlOfResourceList({opCallbackJs => 'searchPopup_itemSelected', opIcon => 'add.gif', opTitle => $i18n->get('resource add opTitle'), hiddenFields => 0}, @resources).'
'; + } else { + $var->{doingSearch} = 0; + } + + return $self->processTemplate($var, $self->getValue('resourcePopupTemplateId')); +} + +#------------------------------------------------------------------- +sub _userSearchQuery { + my $self = shift; + my $exclude = shift; + my $searchPattern = lc('%'.shift().'%'); + my @exclude = ('1', '3', split /\;/, $exclude); + my $excludePlaceholders = '('.join(',', map{'?'} @exclude).')'; + + my $query = <<"SQL"; +SELECT 'user' AS resourceKind, users.userId AS resourceId + FROM users + LEFT JOIN userProfileData AS lastName ON users.userId = lastName.userId + AND lastName.fieldName = 'lastName' + LEFT JOIN userProfileData AS firstName ON users.userId = firstName.userId + AND firstName.fieldName = 'firstName' + WHERE (LOWER(lastName.fieldData) LIKE ? OR LOWER(firstName.fieldData) LIKE ? + OR LOWER(users.username) LIKE ?) AND (users.userId NOT IN $excludePlaceholders) + ORDER BY lastName.fieldData, firstName.fieldData +SQL + my @placeholders = (($searchPattern) x 3, @exclude); + return ($query, \@placeholders); +} + +sub www_userSearchPopup { + my $self = shift; + + my %args = (func => 'userSearchPopup', + i18nprefix => 'user add popup', + queryCallback => sub { $self->_userSearchQuery(@_) }, + ); + $self->_resourceSearchPopup(%args); +} + +#------------------------------------------------------------------- +sub _groupSearchQuery { + my $self = shift; + my $exclude = shift; + my $searchPattern = lc('%'.shift().'%'); + my @exclude = ('1', '7', split /\;/, $exclude); + my $excludePlaceholders = '('.join(',', map{'?'} @exclude).')'; + my $query = <<"SQL"; +SELECT 'group' AS resourceKind, groups.groupId AS resourceId + FROM groups + WHERE (LOWER(groups.groupName) LIKE ?) AND (groups.groupId NOT IN $excludePlaceholders) + AND groups.isEditable = 1 + ORDER BY groups.groupName +SQL + my @placeholders = ($searchPattern, @exclude); + return ($query, \@placeholders); +} + +sub www_groupSearchPopup { + my $self = shift; + my %args = (func => 'groupSearchPopup', + i18nprefix => 'group add popup', + queryCallback => sub { $self->_groupSearchQuery(@_) }, + ); + $self->_resourceSearchPopup(%args); +} + +#------------------------------------------------------------------- +sub _resourceListOfTask { + # TODO: Should there be a getAllCollateral in Asset::Wobject? + my $self = shift; + my $taskId = shift; + return ($taskId eq 'new')? () : + @{$self->session->db->buildArrayRefOfHashRefs("SELECT resourceKind, resourceId FROM PM_taskResource WHERE taskId = ? ORDER BY sequenceNumber", [$taskId])}; +} + +sub _innerHtmlOfResources { + my $self = shift; + my @resources = @_; + my $i18n = WebGUI::International->new($self->session, 'Asset_ProjectManager'); + return $self->_htmlOfResourceList({opCallbackJs => 'taskEdit_deleteResource', opIcon => 'delete.gif', opTitle => $i18n->get('resource remove opTitle'), hiddenFields => 1}, @resources); +} + +sub www_innerHtmlOfResources { + my $self = shift; + my @resources = map { + my ($resourceKind, $resourceId) = split / /, $_, 2; + { resourceKind => $resourceKind, resourceId => $resourceId } + } split /\;/, $self->session->form->param('resources'); + return $self->_innerHtmlOfResources(@resources); +} + sub www_editTask { my $self = shift; my $var = {}; @@ -668,12 +876,26 @@ sub www_editTask { tie my %users, "Tie::IxHash"; %users = $db->buildHash("select userId,username from users where userId not in ('1','3') order by userId"); %users = (""=>$i18n->get("resource none"),%users); - $var->{'form.resource'} = WebGUI::Form::selectBox($session, { - -name=>"resource", - -options=>\%users, - -value=>[$task->{resourceId}], - -extras=>$extras - }); + + my @resources = $self->_resourceListOfTask($taskId); + my ($searchUserUrlHtml, $searchGroupUrlHtml) = map { + my $kind = $_; + my $exclude = $self->session->url->escape(join ';', map {$_->{resourceId}} grep {$_->{resourceKind} eq $kind} @resources); + my $func = $kind.'SearchPopup'; + WebGUI::HTML::format($self->getUrl("func=$func;callback=taskEdit_queueAddResource;exclude=$exclude"), 'text'); + } (qw/user group/); + + $var->{'form.addUser.id'} = 'taskEdit_resourceList_addUser_a'; + $var->{'form.addUser.link'} = $searchUserUrlHtml; + $var->{'form.addUser.text'} = $i18n->get('user add popup hover'); + + $var->{'form.addGroup.id'} = 'taskEdit_resourceList_addGroup_a'; + $var->{'form.addGroup.link'} = $searchGroupUrlHtml; + $var->{'form.addGroup.text'} = $i18n->get('group add popup hover'); + + $var->{'form.resourceDiv'} = + '
'.$self->_innerHtmlOfResources(@resources).'
'; + $var->{'form.milestone'} = WebGUI::Form::checkbox($session, { -name=>"milestone", -value=>1, @@ -692,7 +914,7 @@ sub www_editTask { $var->{'form.footer'} = WebGUI::Form::formFooter($session); $var->{'extras'} = $config->get("extrasURL"); - +$var->{'assetExtras'} = $config->get("extrasURL").'/wobject/ProjectManager'; return $self->processTemplate($var,$self->getValue("editTaskTemplateId")) } @@ -722,7 +944,7 @@ sub www_editTaskSave { $props->{endDate} = ($isMilestone ? $props->{startDate} : $form->process("end","date")); $props->{dependants} = $form->process("dependants","selectBox") unless $isMilestone; $props->{isMilestone} = $isMilestone || 0; - $props->{resourceId} = $form->process("resource","selectBox"); + my @resourceSpecs = $form->process("resources","hiddenList"); $props->{percentComplete} = $isMilestone? 0 : $form->process("percentComplete","float"); my $now = $dt->time(); @@ -735,6 +957,12 @@ sub www_editTaskSave { #Save the extended task data my $taskId = $self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId); + $self->deleteCollateral('PM_taskResource', 'taskId', $taskId); + foreach my $resourceSpec (@resourceSpecs) { + # Buggo, should probably factor the common SQL out of the loop + my ($resourceKind, $resourceId) = split / /, $resourceSpec, 2; + $self->setCollateral('PM_taskResource', 'taskResourceId', {taskId => $taskId, resourceKind => $resourceKind, resourceId => $resourceId}, 1, 0, 'taskId', $taskId); + } #Reorder tasks if task is inserted my $insertAt = $form->get("insertAt"); @@ -918,6 +1146,7 @@ sub www_viewProject { #Set Some Style stuff $style->setLink($assetExtras."/subModal.css",{rel=>"stylesheet",type=>"text/css"}); + $style->setLink($assetExtras."/taskEdit.css",{rel=>"stylesheet",type=>"text/css"}); $style->setLink($extras."/calendar/calendar-win2k-1.css",{rel=>"stylesheet",type=>"text/css"}); $style->setLink($assetExtras."/cMenu.css",{rel=>"stylesheet",type=>"text/css"}); @@ -928,6 +1157,7 @@ sub www_viewProject { $style->setScript($extras."/contextMenu/contextMenu.js",{ type=>"text/javascript" }); $style->setScript($extras."/calendar/lang/calendar-en.js",{ type=>"text/javascript" }); $style->setScript($extras."/calendar/calendar-setup.js",{ type=>"text/javascript" }); + $style->setScript($assetExtras."/taskEdit.js",{ type=>"text/javascript" }); #Get Project Data my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId)); @@ -990,7 +1220,7 @@ sub www_viewProject { -value=>$startDate, -size=>"10", -maxlength=>"10", - -extras=>qq|onfocus="doCalendar(this.id);" class="taskdate" onblur="adjustTaskTimeFromDate(this,document.getElementById('$endId'),document.getElementById('$durId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');"| + -extras=>qq }); $hash->{'task.start'} .= WebGUI::Form::hidden($session,{ @@ -1105,6 +1335,38 @@ sub www_viewProject { } #------------------------------------------------------------------- +sub _doGanttTaskResourceDisplay { + my $self = shift; + my $hash = shift; + my $task = shift; + my @resources = $self->_resourceListOfTask($task->{taskId}); + my @resourceNames = (); + + foreach my $resource (@resources) { + my ($resourceKind, $resourceId) = @$resource{qw{resourceKind resourceId}}; + if ($resourceKind eq 'user') { + my $u = WebGUI::User->new($self->session, $resourceId); + my $name = $u->username; + my $firstName = $u->profileField('firstName'); + my $lastName = $u->profileField('lastName'); + $name = "$firstName $lastName" if ($firstName && $lastName); + push @resourceNames, $name; + } elsif ($resourceKind eq 'group') { + my $g = WebGUI::Group->new($self->session, $resourceId); + push @resourceNames, $g->name; + } else { + # Whee. + push @resourceNames, "???" + } + } + + if (@resources) { + $hash->{'task.hasResource'} = "true"; + $hash->{'task.resource.name'} = + join(', ', map { WebGUI::HTML::format($_, 'text') } @resourceNames); + } +} + sub www_drawGanttChart { my $self = shift; my $var = {}; @@ -1228,28 +1490,15 @@ sub www_drawGanttChart { my $taskHash = {}; foreach my $task (@{$taskList}) { - my $hash = {}; + my $hash = {}; my $id = $task->{taskId}; my $seq = $task->{sequenceNumber}; my $startDate = $task->{startDate}; my $endDate = $task->{endDate}; my $duration = $task->{duration}; my $predecessor = $task->{dependants}; - my $resource = $task->{resourceId}; - - if($resource) { - $hash->{'task.hasResource'} = "true"; - my $u = WebGUI::User->new($session,$resource); - my $username = $u->username; - - my $firstName = $u->profileField('firstName'); - my $lastName = $u->profileField('lastName'); - if($firstName && $lastName) { - $username = $firstName." ".$lastName; - } - $hash->{'task.resource.name'} = $username; - } - + $self->_doGanttTaskResourceDisplay($hash, $task); + my $durationFloor = floor($duration); $duration = $duration / $hoursPerDay if( $dunits == "hours" ); #Set duration to 1 day if it's a milestone diff --git a/lib/WebGUI/i18n/English/Asset_ProjectManager.pm b/lib/WebGUI/i18n/English/Asset_ProjectManager.pm index 7ea38abf9..e50ace60b 100644 --- a/lib/WebGUI/i18n/English/Asset_ProjectManager.pm +++ b/lib/WebGUI/i18n/English/Asset_ProjectManager.pm @@ -41,6 +41,26 @@ our $I18N = { lastUpdated => 0 }, + 'resourcePopupTemplate hoverhelp' => { + message => q|Template to use for task resource selection popups.|, + lastUpdated => 1157510786 + }, + + 'resourcePopupTemplate label' => { + message => q|Default Resource Popup Template|, + lastUpdated => 1157510786 + }, + + 'resourceListTemplate hoverhelp' => { + message => q|Template to use for displaying resource lists. Used by the resource popup template and the edit task template.|, + lastUpdated => 1157510786 + }, + + 'resourceListTemplate label' => { + message => q|Default Resource List Template|, + lastUpdated => 1157510786 + }, + 'groupToAdd hoverhelp' => { message => q|Group that is able to create projects|, lastUpdated => 0 @@ -710,6 +730,65 @@ Otherwise, just the duration will be displayed as text.|, lastUpdated => 1149825108 }, + 'resource add opTitle' => { + message => q|Add to Task|, + lastUpdated => 1157510786 + }, + + 'resource remove opTitle' => { + message => q|Remove from Task|, + lastUpdated => 1157510786 + }, + + 'user add popup hover' => { + message => q|Add User to Task|, + lastUpdated => 1157510786 + }, + + 'group add popup hover' => { + message => q|Add Group to Task|, + lastUpdated => 1157510786 + }, + + 'user add popup title' => { + message => q|Search for User|, + lastUpdated => 1157510786 + }, + + 'user add popup searchText' => { + message => q|Search for user: |, + lastUpdated => 1157510786 + }, + + 'user add popup foundMessage' => { + message => q|Matching users: |, + lastUpdated => 1157510786 + }, + + 'user add popup notFoundMessage' => { + message => q|No matching users found.|, + lastUpdated => 1157510786 + }, + + 'group add popup title' => { + message => q|Search for Group|, + lastUpdated => 1157510786 + }, + + 'group add popup searchText' => { + message => q|Search for group: |, + lastUpdated => 1157510786 + }, + + 'group add popup foundMessage' => { + message => q|Matching groups: |, + lastUpdated => 1157510786 + }, + + 'group add popup notFoundMessage' => { + message => q|No matching groups found.|, + lastUpdated => 1157510786 + }, }; 1; diff --git a/www/extras/wobject/ProjectManager/cMenu.js b/www/extras/wobject/ProjectManager/cMenu.js index c3432e6c3..47ccad106 100644 --- a/www/extras/wobject/ProjectManager/cMenu.js +++ b/www/extras/wobject/ProjectManager/cMenu.js @@ -302,7 +302,7 @@ function cMenu_draw(){ var output = ""; output += '
'; for (i=0;i z4__U4_Up*=|N9td2^4>_FfuSCFzA3R0mTUe`@e<+1c(m}35`T>6C#}5-0VSoFCU-S zFefJ`T^9=vAlFAoh|5;n#zxKD*1!+QS68t#)6mh;QPZ-r2nO*jU0sZ|TwS#^%}qh( z8G!i8F4oG*<{<3}K4z|#j)tZd(vsp0G9U%+W@c)(s-^}K3W|z;3GrT>rh2^mZ2BDX zfq_c$k-|(&jO+|zVqAQJAigJ%%f!UQEXFD(Af+G|%>(2B0fQKe7=Jj>umBD)pHYCB i&pQfaT>w}GPkd}VhzA6*o}!{&u^<*(1Bh&7um%A2Zq(BN literal 0 HcmV?d00001 diff --git a/www/extras/wobject/ProjectManager/taskEdit.css b/www/extras/wobject/ProjectManager/taskEdit.css new file mode 100644 index 000000000..70d31ef15 --- /dev/null +++ b/www/extras/wobject/ProjectManager/taskEdit.css @@ -0,0 +1,30 @@ +#taskEdit_resourceList_div { + overflow: auto; + width: 100%; + height: 100px; + font-family: arial; + font-size: 10pt; + border: solid #CACACA 1px; +} + +* html #taskEdit_resourceList_div { + overflow: hidden; + overflow-y: auto; +} + +#taskEdit_resourceList_div img { + padding-right: 10px; + padding-left: 10px; +} + +#taskEdit_resourceList_div td { + background-color: #F2F2F2; + border-top: solid #F9F9F9 1px; + border-bottom: solid #E0E0E0 1px; + text-align: left; + vertical-align: top; +} + +#taskEdit_resourceList_div tr.odd td { + background-color: #EAEAEA; +} diff --git a/www/extras/wobject/ProjectManager/taskEdit.js b/www/extras/wobject/ProjectManager/taskEdit.js new file mode 100644 index 000000000..c21e2c092 --- /dev/null +++ b/www/extras/wobject/ProjectManager/taskEdit.js @@ -0,0 +1,111 @@ +var taskEdit_inited = 0; +var taskEdit_pending = null; + +function taskEdit_getResourceListDiv() { + return document.getElementById('taskEdit_resourceList_div'); +} + +function taskEdit_searchPopup(url) { + window.open(url, null, 'status=1,toolbar=0,location=0,menubar=0,directories=0,resizable=1,height=350,width=400'); +} + +function taskEdit_getResources() { + var elts = taskEdit_getResourceListDiv().getElementsByTagName('input'); + var resources = []; + + for (var i = 0; i < elts.length; i++) { + if (elts[i].getAttribute('type') == 'hidden' && + elts[i].getAttribute('name') == 'resources') + resources[i] = elts[i].getAttribute('value'); + } + + return resources; +} + +function taskEdit_updateExclude(id, kind, resources) { + var elt = document.getElementById(id); + if (!elt) return; + + var resourceIds = []; + for (var i = 0; i < resources.length; i++) { + var split = resources[i].split(' ', 2); + if (split[0] == kind) + resourceIds.push(split[1]); + } + var exclude = resourceIds.join(';'); + + var href = elt.getAttribute('href'); + href = href.replace(/([?;&]exclude=)[^;&]*/, function(str, p1, offset, s) { + return p1 + encodeURIComponent(exclude); + }); + elt.setAttribute('href', href); +} + +function taskEdit_updateResources(resources) { + var div = taskEdit_getResourceListDiv(); + var savedInnerHTML = div.innerHTML; + div.innerHTML = "

Please wait…

"; + + var component = encodeURIComponent(resources.join(';')); + var url = document.location.toString(); + var queryIndex = url.indexOf('?'); + if (queryIndex > -1) url = url.substr(0, queryIndex); + url += '?func=innerHtmlOfResources;resources=' + component; + taskEdit_updateExclude("taskEdit_resourceList_addUser_a", 'user', resources); + taskEdit_updateExclude("taskEdit_resourceList_addGroup_a", 'group', resources); + + taskEdit_pending = []; + AjaxRequest.get({ + 'url' : url, + 'onSuccess' : function(req) { + div.innerHTML = req.responseText; + taskEdit_doPending(); + }, + 'onError' : function(req) { + // Buggo: need better error handling + div.innerHTML = savedInnerHTML; + taskEdit_doPending(); + } + }); +} + +function taskEdit_doPending() { + for (var i = 0; i < taskEdit_pending.length; i++) { + taskEdit_pending[i](); + } + + taskEdit_pending = null; +} + +function taskEdit_addResource(kind, id) { + if (taskEdit_pending != null) { + taskEdit_pending.push(function() { taskEdit_addResource(kind, id) }); + return; + } + + var string = kind+' '+id; + var resources = taskEdit_getResources(); + resources.push(string); + taskEdit_updateResources(resources); +} + +function taskEdit_queueAddResource(kind, id) { + window.setTimeout(function() { taskEdit_addResource(kind, id) }, 0); +} + +function taskEdit_deleteResource(kind, id) { + if (taskEdit_pending != null) { + taskEdit_pending.push(function() { taskEdit_deleteResource(kind, id) }); + return; + } + + var string = kind+' '+id; + var resources = taskEdit_getResources(); + for (var i = 0; i < resources.length; i++) { + if (resources[i] == string) { + resources.splice(i, 1); + break; + } + } + taskEdit_updateResources(resources); +} diff --git a/www/extras/wobject/ProjectManager/users.gif b/www/extras/wobject/ProjectManager/users.gif new file mode 100644 index 0000000000000000000000000000000000000000..831d360405c38f3f5f96ea66ecaf67cb69ecfd91 GIT binary patch literal 680 zcmZ?wbhEHb6krfw_~y)@6JM#5Gtnrw$F6IwZ{OaiSzF^4@5`UHGG*qb(s^qWmh3B9 zusv$-k(dR?QdS<%-gGv9+oi%C=SufntUY?8FPL+C1moU(j%5i-n{zd0RR%6DbK6np zdmxtKd>Z57YOZV943|oT9~Loxs$$sFtG>O@WKLJk-U(4>TX-%uaK2nEc4l4Nxor_g zH&>iLP}#m>ZSR)#ZJRf4UeU07$F!A4x9z>WxAD~NQ~MfD9i4UQ