Index: app/controllers/issues_controller.rb =================================================================== --- app/controllers/issues_controller.rb (revision 2671) +++ app/controllers/issues_controller.rb (working copy) @@ -18,10 +18,10 @@ class IssuesController < ApplicationController menu_item :new_issue, :only => :new - before_filter :find_issue, :only => [:show, :edit, :reply] + before_filter :find_issue, :only => [:show, :edit, :reply, :edit_gantt] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] before_filter :find_project, :only => [:new, :update_form, :preview] - before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] + before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :edit_gantt] before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] accept_key_auth :index, :changes @@ -159,6 +159,64 @@ # Attributes that can be updated on workflow transition (without :edit permission) # TODO: make it configurable (at least per role) UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION) + + def edit_gantt + if !@issue.start_date || !@issue.due_before + render :text=>l(:notice_locking_conflict), :status=>400 + return + end + @issue.init_journal(User.current) + date_from = Date.parse(params[:date_from]) + old_start_date = @issue.start_date + o = get_position(@issue, date_from, Date.parse(params[:date_to]), params[:zoom]) + text_for_revert = "#{@issue.id}=#{format_date(@issue.start_date)},#{@issue.start_date},#{format_date(@issue.due_before)},#{@issue.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}" + + if params[:day] + #bar moved + day = params[:day].to_i + duration = @issue.due_before - @issue.start_date + @issue.start_date = date_from + day + @issue.due_date = @issue.start_date + duration.to_i if @issue.due_date + elsif params[:start_date] + #start date changed + start_date = Date.parse(params[:start_date]) + if @issue.start_date == start_date + render :text=>"" + return #nothing has changed + end + @issue.start_date = start_date + @issue.due_date = start_date if @issue.due_date && start_date > @issue.due_date + elsif params[:due_date] + #due date changed + due_date = Date.parse(params[:due_date]) + if @issue.due_date == due_date + render :text=>"" + return #nothing has changed + end + @issue.due_date = due_date + @issue.start_date = due_date if due_date < @issue.start_date + end + fv = @issue.fixed_version + if fv && fv.effective_date && !@issue.due_date && fv.effective_date < @issue.start_date + @issue.start_date = old_start_date + end + + begin + @issue.save! + o = get_position(@issue, date_from, Date.parse(params[:date_to]), params[:zoom]) + text = "#{@issue.id}=#{format_date(@issue.start_date)},#{@issue.start_date},#{format_date(@issue.due_before)},#{@issue.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}" + + #check dependencies + issues = @issue.all_precedes_issues + issues.each do |i| + o = get_position(i, date_from, Date.parse(params[:date_to]), params[:zoom]) + text += "|#{i.id}=#{format_date(i.start_date)},#{i.start_date},#{format_date(i.due_before)},#{i.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}" + end + render :text=>text + rescue + render :text=>@issue.errors.full_messages.join("\n") + "|" + text_for_revert , :status=>400 + end + end def edit @allowed_statuses = @issue.new_statuses_allowed_to(User.current) Index: app/helpers/issues_helper.rb =================================================================== --- app/helpers/issues_helper.rb (revision 2671) +++ app/helpers/issues_helper.rb (working copy) @@ -27,8 +27,8 @@ @cached_label_priority ||= l(:field_priority) link_to_issue(issue) + ": #{h(issue.subject)}

" + - "#{@cached_label_start_date}: #{format_date(issue.start_date)}
" + - "#{@cached_label_due_date}: #{format_date(issue.due_date)}
" + + "#{@cached_label_start_date}: #{format_date(issue.start_date)}
" + + "#{@cached_label_due_date}: #{format_date(issue.due_date)}
" + "#{@cached_label_assigned_to}: #{issue.assigned_to}
" + "#{@cached_label_priority}: #{issue.priority.name}" end @@ -196,4 +196,26 @@ export.rewind export end + + def get_position(event, date_from, date_to, zoom_str) + zoom = zoom_str.to_i + i_start_date = (event.start_date >= date_from ? event.start_date : date_from ) + i_end_date = (event.due_before <= date_to ? event.due_before : date_to ) + + i_done_date = event.start_date + ((event.due_before - event.start_date+1)*event.done_ratio/100).floor + i_done_date = (i_done_date <= date_from ? date_from : i_done_date ) + i_done_date = (i_done_date >= date_to ? date_to : i_done_date ) + + i_late_date = [i_end_date, Date.today].min if i_start_date <= Date.today + + i_left = ((i_start_date - date_from)*zoom).floor + i_left = 0 if i_left < 0 + i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders) + i_width = 0 if i_width < 0 + d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width + d_width = 0 if d_width < 0 + l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width + l_width = 0 if l_width < 0 + return i_left, i_width, l_width, d_width + end end Index: app/models/issue.rb =================================================================== --- app/models/issue.rb (revision 2671) +++ app/models/issue.rb (working copy) @@ -240,6 +240,16 @@ end dependencies end + + def all_precedes_issues + dependencies = [] + relations_from.each do |relation| + next unless relation.relation_type == IssueRelation::TYPE_PRECEDES + dependencies << relation.issue_to + dependencies += relation.issue_to.all_dependent_issues + end + dependencies + end # Returns an array of issues that duplicate this one def duplicates Index: app/views/issues/gantt.rhtml =================================================================== --- app/views/issues/gantt.rhtml (revision 2671) +++ app/views/issues/gantt.rhtml (working copy) @@ -1,3 +1,4 @@ +<% include_calendar_headers_tags %> <% form_tag(params.merge(:month => nil, :year => nil, :months => nil), :id => 'query_form') do %> <% if @query.new_record? %>

<%=l(:label_gantt)%>

@@ -31,14 +32,14 @@

-<%= link_to_remote l(:button_apply), +<%= link_to_remote l(:button_apply), { :url => { :set_filter => (@query.new_record? ? 1 : nil) }, :update => "content", :with => "Form.serialize('query_form')" }, :class => 'icon icon-checked' %> - + <%= link_to_remote l(:button_clear), - { :url => { :set_filter => (@query.new_record? ? 1 : nil) }, + { :url => { :set_filter => (@query.new_record? ? 1 : nil) }, :update => "content", }, :class => 'icon icon-reload' if @query.new_record? %>

@@ -49,7 +50,7 @@ <% zoom = 1 @gantt.zoom.times { zoom = zoom * 2 } -subject_width = 330 +subject_width = 280 header_heigth = 18 headers_height = header_heigth @@ -69,7 +70,106 @@ g_height = [(20 * @gantt.events.length + 6)+150, 206].max t_height = g_height + headers_height %> + + + + + +
@@ -83,7 +183,7 @@ # top = headers_height + 8 @gantt.events.each do |i| %> -
+
<% if i.is_a? Issue %> <%= h("#{i.project} -") unless @project && @project == i.project %> <%= link_to_issue i %>: <%=h i.subject %> @@ -92,35 +192,82 @@ <%= h("#{i.project} -") unless @project && @project == i.project %> <%= link_to_version i %> - <% end %> + <% end %>
<% top = top + 20 end %>
+
+
+
+<% +top = headers_height + 8 +@gantt.events.each do |i| %> +
+ <% if i.is_a? Issue %> + <%= format_date(i.start_date) %> + + <%= calendar_for("#{i.id}_start_date") if i.is_a? Issue %> + + "> + <%= format_date(i.due_before) %> + + + <%= calendar_for("#{i.id}_due_date") if i.due_date%> + + <% else %> + <%= format_date(i.start_date) %> + <% end %> +
+<% top = top + 20 +end %> +
+
-
+
 
-<% +<% # # Months headers # month_f = @gantt.date_from left = 0 height = (show_weeks ? header_heigth : header_heigth + g_height) -@gantt.months.times do +@gantt.months.times do width = ((month_f >> 1) - month_f) * zoom - 1 %>
<%= link_to "#{month_f.year}-#{month_f.month}", @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%>
- <% + <% left = left + width + 1 month_f = month_f >> 1 end %> -<% +<% # # Weeks headers # @@ -136,7 +283,7 @@ width = (7 - @gantt.date_from.cwday + 1) * zoom-1 %>
 
- <% + <% left = left + width+1 end %> <% @@ -146,13 +293,13 @@
<%= week_f.cweek if width >= 16 %>
- <% + <% left = left + width+1 week_f = week_f+7 end end %> -<% +<% # # Days headers # @@ -160,15 +307,17 @@ left = 0 height = g_height + header_heigth - 1 wday = @gantt.date_from.cwday - (@gantt.date_to - @gantt.date_from + 1).to_i.times do + dt = @gantt.date_from + (@gantt.date_to - @gantt.date_from + 1).to_i.times do width = zoom - 1 %>
5 %>" class="gantt_hdr"> - <%= day_name(wday).first %> + <%= "#{dt.day}
"if @gantt.zoom == 4 %><%= day_name(wday).first %>
- <% + <% left = left + width+1 wday = wday + 1 + dt = dt + 1 wday = 1 if wday > 7 end end %> @@ -178,41 +327,36 @@ # Tasks # top = headers_height + 10 -@gantt.events.each do |i| - if i.is_a? Issue - i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from ) - i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to ) - - i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor - i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date ) - i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date ) - - i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today - - i_left = ((i_start_date - @gantt.date_from)*zoom).floor - i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders) - d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width - l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width +@gantt.events.each do |i| + if i.is_a? Issue + i_left, i_width, l_width, d_width = get_position(i, @gantt.date_from, @gantt.date_to, zoom) %> -
 
- <% if l_width > 0 %> -
 
- <% end %> - <% if d_width > 0 %> -
 
- <% end %> -
- <%= i.status.name %> - <%= (i.done_ratio).to_i %>% -
-
- +
+
 
+
 
+
 
+
+ <%= h(i.status.name) %> + <%= (i.done_ratio).to_i %>% + <% if !i.due_date && i.fixed_version %> + - <%= h(i.fixed_version.name) %> + <% end %> +
+
+ <%# === tooltip === %> +
+ <%= render_issue_tooltip i %>
-<% else + <%= draggable_element("ev_#{i.id}", + :revert =>false, :scroll=>"'gantt-container'", :constraint => "'horizontal'", :snap=>zoom, + :onEnd=>"function( draggable, event ) {issue_moved(draggable.element);}" + ) %> + +<% else i_left = ((i.start_date - @gantt.date_from)*zoom).floor %> -
 
+
 
<%= h("#{i.project} -") unless @project && @project == i.project %> <%=h i %> Index: public/stylesheets/application.css =================================================================== --- public/stylesheets/application.css (revision 2671) +++ public/stylesheets/application.css (working copy) @@ -639,6 +639,8 @@ .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; } .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; } .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; } +.task_none { background:transparent; border-style: none; } +.task_none { background:transparent; border-style: none; } .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; } /***** Icons *****/