Project

General

Profile

Feature #2024 » gantt_edit_r3764.patch

Hiroyuki Yoshioka, 2010-05-29 17:57

View differences:

app/controllers/gantts_controller.rb (working copy)
1 1
class GanttsController < ApplicationController
2 2
  before_filter :find_optional_project
3
  before_filter :find_issue, :only => [:edit_gantt]
3 4

  
4 5
  rescue_from Query::StatementInvalid, :with => :query_statement_invalid
5 6

  
......
10 11
  helper :sort
11 12
  include SortHelper
12 13
  include Redmine::Export::PDF
14
  include IssuesHelper
13 15
  
14 16
  def show
15 17
    @gantt = Redmine::Helpers::Gantt.new(params)
......
42 44
    end
43 45
  end
44 46

  
47
  def find_optional_project
48
    @project = Project.find(params[:project_id]) unless params[:project_id].blank?
49
    if params[:action] == "edit_gantt"
50
      allowed = User.current.allowed_to?({:controller => 'issues', :action => 'edit'}, @project, :global => true)
51
    else
52
      allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
53
    end
54
    allowed ? true : deny_access
55
  rescue ActiveRecord::RecordNotFound
56
    render_404
57
  end
58

  
59
  def edit_gantt
60
    if !@issue.start_date || !@issue.due_before
61
      render :text=>l(:notice_locking_conflict), :status=>400
62
      return
63
    end
64
    @issue.init_journal(User.current)
65
    date_from = Date.parse(params[:date_from])
66
    old_start_date = @issue.start_date
67
    o = get_position(@issue, date_from, Date.parse(params[:date_to]), params[:zoom])
68
    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]}"
69

  
70
    if params[:day]
71
      #bar moved
72
      day = params[:day].to_i
73
      duration = @issue.due_before - @issue.start_date
74
      @issue.start_date = date_from + day
75
      @issue.due_date = @issue.start_date + duration.to_i if @issue.due_date
76
    elsif params[:start_date]
77
      #start date changed
78
      start_date = Date.parse(params[:start_date])
79
      if @issue.start_date == start_date
80
        render :text=>""
81
        return #nothing has changed
82
      end
83
      @issue.start_date = start_date
84
      @issue.due_date = start_date if @issue.due_date && start_date > @issue.due_date
85
    elsif params[:due_date]
86
      #due date changed
87
      due_date = Date.parse(params[:due_date])
88
      if @issue.due_date == due_date
89
        render :text=>""
90
        return #nothing has changed
91
      end
92
      @issue.due_date = due_date
93
      @issue.start_date = due_date if due_date < @issue.start_date
94
    end
95
    fv = @issue.fixed_version
96
    if fv && fv.effective_date && !@issue.due_date && fv.effective_date < @issue.start_date
97
      @issue.start_date = old_start_date
98
    end
99

  
100
    begin
101
      @issue.save!
102
      o = get_position(@issue, date_from, Date.parse(params[:date_to]), params[:zoom])
103
      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]}"
104

  
105
      #check dependencies
106
      issues = @issue.all_precedes_issues
107
      issues.each do |i|
108
        o = get_position(i, date_from, Date.parse(params[:date_to]), params[:zoom])
109
        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]}"
110
      end
111
      render :text=>text
112
    rescue
113
      render :text=>@issue.errors.full_messages.join("\n") + "|" + text_for_revert  , :status=>400
114
    end
115
  end
116

  
117
private
118
  def find_issue
119
    @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
120
    @project = @issue.project
121
  rescue ActiveRecord::RecordNotFound
122
    render_404
123
  end
124

  
45 125
end
app/helpers/issues_helper.rb (working copy)
36 36
    @cached_label_priority ||= l(:field_priority)
37 37
    
38 38
    link_to_issue(issue) + "<br /><br />" +
39
      "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
40
      "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
39
      "<strong>#{@cached_label_start_date}</strong>: <span id='tooltip_start_date_#{issue.id}'>#{format_date(issue.start_date)}</span><br />" +
40
      "<strong>#{@cached_label_due_date}</strong>: <span id='tooltip_due_date_#{issue.id}'>#{format_date(issue.due_date)}</span><br />" +
41 41
      "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
42 42
      "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
43 43
  end
......
259 259
      end
260 260
    end
261 261
  end
262

  
263
  def get_position(event, date_from, date_to, zoom_str)
264
    zoom = zoom_str.to_i
265
    i_start_date = (event.start_date >= date_from ? event.start_date : date_from )
266
    i_end_date = (event.due_before <= date_to ? event.due_before : date_to )
267

  
268
    i_done_date = event.start_date + ((event.due_before - event.start_date+1)*event.done_ratio/100).floor
269
    i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
270
    i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
271

  
272
    i_late_date = [i_end_date, Date.today].min if i_start_date <= Date.today
273

  
274
    i_left = ((i_start_date - date_from)*zoom).floor
275
    i_left = 0 if i_left < 0
276
    i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2                  # total width of the issue (- 2 for left and right borders)
277
    i_width = 0 if i_width < 0
278
    d_width = ((i_done_date - i_start_date)*zoom).floor - 2                     # done width
279
    d_width = 0 if d_width < 0
280
    l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
281
    l_width = 0 if l_width < 0
282
    return i_left, i_width, l_width, d_width
283
  end
262 284
end
app/models/issue.rb (working copy)
415 415
    end
416 416
    dependencies
417 417
  end
418

  
419
  def all_precedes_issues
420
    dependencies = []
421
    relations_from.each do |relation|
422
      next unless relation.relation_type == IssueRelation::TYPE_PRECEDES
423
      dependencies << relation.issue_to
424
      dependencies += relation.issue_to.all_dependent_issues
425
    end
426
    dependencies
427
  end
418 428
  
419 429
  # Returns an array of issues that duplicate this one
420 430
  def duplicates
app/views/gantts/show.html.erb (working copy)
1
<% include_calendar_headers_tags %>
1 2
<h2><%= l(:label_gantt) %></h2>
2 3

  
3 4
<% form_tag(params.merge(:month => nil, :year => nil, :months => nil), :id => 'query_form') do %>
......
38 39
<% zoom = 1
39 40
@gantt.zoom.times { zoom = zoom * 2 }
40 41

  
41
subject_width = 330
42
subject_width = 280
42 43
header_heigth = 18
43 44

  
44 45
headers_height = header_heigth
......
58 59
g_height = [(20 * @gantt.events.length + 6)+150, 206].max
59 60
t_height = g_height + headers_height
60 61
%>
62
<input type="hidden" name="_date_from" id="i_date_from" value="<%=h(@gantt.date_from)%>" />
63
<input type="hidden" name="_date_to" id="i_date_to" value="<%=h(@gantt.date_to)%>" />
64
<input type="hidden" name="zm" id="i_zm" value="<%=h(zoom)%>" />
65
<script type='text/javascript'>
66
  function issue_moved(elem) {
67
    var id_str = elem.id.substring(3, elem.id.length);
68
    var v_date_from = document.getElementById('i_date_from').getAttribute("value");
69
    var v_date_to = document.getElementById('i_date_to').getAttribute("value");
70
    var v_zm = document.getElementById('i_zm').getAttribute("value");
71
    var url_str = '/issues/edit_gantt/' + id_str;
72
    var day = parseInt(elem.style.left)/parseInt(v_zm);
73
    new Ajax.Request(url_str, {asynchronous:true, evalScripts:true,
74
      parameters: 'day='+day+'&date_from='+v_date_from+'&date_to='+v_date_to+'&zoom='+v_zm,
75
      onSuccess:function(obj) {
76
        change_dates(obj.responseText);
77
      },
78
      onFailure:function(obj) {
79
        handle_failure(obj.responseText);
80
      }
81
    });
61 82

  
83
  }
84
  
85
  function handle_failure(res_text) {
86
    var text = res_text.split('|');
87
    alert(text[0]);
88
    if (text.length == 1) {
89
      return;
90
    }
91
    change_dates(text[1]);//revert
92
  }
93

  
94
  function change_dates(issue_infos) {
95
    if (!issue_infos) {
96
      return;
97
    }
98
    var issue_list = issue_infos.split("|");
99
    for (i = 0; i < issue_list.length; i++) {
100
      change_date(issue_list[i]);
101
    }
102
  }
103

  
104
  function change_date(text) {
105
    if (!text) {
106
      return;
107
    }
108
    var issue_info = text.split("=");
109
    var elem_id = issue_info[0];
110
    var vals = issue_info[1].split(',');
111
    var start_date_elem = document.getElementById(elem_id + '_start_date_str');
112
    if (!start_date_elem) {
113
      //target not exists
114
      return;
115
    }
116
    start_date_elem.innerHTML = vals[0];
117
    var tooltip_start_date_elem = document.getElementById('tooltip_start_date_' + elem_id);
118
    tooltip_start_date_elem.innerHTML = vals[0];
119
    var due_date_elem = document.getElementById(elem_id + '_due_date_str');
120
    due_date_elem.innerHTML = vals[2];
121
    var tooltip_due_date_elem = document.getElementById('tooltip_due_date_' + elem_id);
122
    tooltip_due_date_elem.innerHTML = vals[2];
123

  
124
    var ev_elem = document.getElementById('ev_' + elem_id);
125
    ev_elem.style.left = vals[4] + 'px';
126
    ev_elem.style.width = (parseInt(vals[5])+100)+'px';
127
    var todo_elem = document.getElementById('task_todo_' + elem_id);
128
    todo_elem.style.width = vals[5] + 'px';
129
    var late_elem = document.getElementById('task_late_' + elem_id);
130
    if (late_elem) {
131
      late_elem.style.width = vals[6] + 'px';
132
      if (vals[6] == '0') {
133
        late_elem.className = 'task task_none';
134
      } else {
135
        late_elem.className = 'task task_late';
136
      }
137
    }
138
    var done_elem = document.getElementById('task_done_' + elem_id);
139
    if (done_elem) {
140
      done_elem.style.width = vals[7] + 'px';
141
      if (vals[7] == '0') {
142
        done_elem.className = 'task task_none';
143
      } else {
144
        done_elem.className = 'task task_done';
145
      }
146
    }
147
    var task_elem = document.getElementById('task_task_' + elem_id);
148
    if (task_elem) {
149
      task_elem.style.left = (parseInt(vals[5])+5) + 'px';
150
    }
151
    var tooltip = document.getElementById("tt_" + elem_id);
152
    tooltip.style.left = ev_elem.style.left;
153

  
154
    //change calendar date
155
    document.getElementById(elem_id+"_hidden_start_date").value = vals[1];
156
    document.getElementById(elem_id+"_start_date").value = vals[1];
157
    document.getElementById(elem_id+"_hidden_due_date").value = vals[3];
158
    document.getElementById(elem_id+"_due_date").value = vals[3];
159
  }
160
</script>
161

  
62 162
<table width="100%" style="border:0; border-collapse: collapse;">
63 163
<tr>
64 164
<td style="width:<%= subject_width %>px; padding:0px;">
......
88 188
end %>
89 189
</div>
90 190
</td>
191
<td style="width:225px; padding:0px;">
192
<div style="position:relative;height:<%= t_height + 24 %>px;width:225px;">
193
<div style="width:225px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
194
<div style="width:225px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
195
<%
196
top = headers_height + 8
197
@gantt.events.each do |i| %>
198
  <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;"><small>
199
  <% if i.is_a? Issue %>
200
    <span id="<%=i.id%>_start_date_str" style="font-size: 7pt;"><%= format_date(i.start_date) %></span>
201
    <input type="hidden" size="12" id="<%=i.id%>_hidden_start_date" value="<%= i.start_date %>">
202
    <input type="hidden" size="12" id="<%=i.id%>_start_date" value="<%= i.start_date %>"><%= calendar_for("#{i.id}_start_date") if i.is_a? Issue %>
203
<script type="text/javascript">
204
//<![CDATA[
205
new Form.Element.Observer('<%=i.id%>_start_date', 0.25,
206
  function(element, value) {
207
    if (value == document.getElementById('<%=i.id%>_hidden_start_date').value) {
208
      return ;
209
    }
210
    new Ajax.Request('<%=url_for(:controller=>:issues, :action => :edit_gantt, :id=>i.id, :date_from=>@gantt.date_from.strftime("%Y-%m-%d"), :date_to=>@gantt.date_to.strftime("%Y-%m-%d"), :zoom=>zoom, :escape => false) %>', {asynchronous:true, evalScripts:true, onFailure:function(request){handle_failure(request.responseText)}, onSuccess:function(request){change_dates(request.responseText)}, parameters:'start_date=' + encodeURIComponent(value)});
211
  })
212
//]]>
213
</script>
214
    <span id="<%=i.id%>_due_date_str" style="font-size: 7pt;<%= "color: blue;" unless i.due_date%>">
215
      <%= format_date(i.due_before) %>
216
    </span>
217
    <input type="hidden" size="12" id="<%=i.id%>_hidden_due_date" value="<%= i.due_date %>">
218
    <input type="hidden" size="12" id="<%=i.id%>_due_date" value="<%= i.due_date %>"><%= calendar_for("#{i.id}_due_date") if i.due_date%>
219
<script type="text/javascript">
220
//<![CDATA[
221
new Form.Element.Observer('<%=i.id%>_due_date', 0.25,
222
  function(element, value) {
223
    if (value == document.getElementById('<%=i.id%>_hidden_due_date').value) {
224
      return ;
225
    }
226
    new Ajax.Request('<%=url_for(:controller=>:issues, :action => :edit_gantt, :id=>i.id, :date_from=>@gantt.date_from.strftime("%Y-%m-%d"), :date_to=>@gantt.date_to.strftime("%Y-%m-%d"), :zoom=>zoom, :escape => false) %>', {asynchronous:true, evalScripts:true, onFailure:function(request){handle_failure(request.responseText)}, onSuccess:function(request){change_dates(request.responseText)}, parameters:'due_date=' + encodeURIComponent(value)});
227
  })
228
//]]>
229
</script>
230
  <% else %>
231
    <span style="font-size: 7pt;"><%= format_date(i.start_date) %></span>
232
  <% end %>
233
  </small></div>
234
<% top = top + 20
235
end %>
236
</div>
237
</td>
91 238
<td>
92 239

  
93
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
240
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt-container">
94 241
<div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
95 242
<% 
96 243
#
......
150 297
	left = 0
151 298
	height = g_height + header_heigth - 1
152 299
	wday = @gantt.date_from.cwday
300
        dt = @gantt.date_from
153 301
	(@gantt.date_to - @gantt.date_from + 1).to_i.times do 
154 302
	width =  zoom - 1
155 303
	%>
156 304
	<div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %>" class="gantt_hdr">
157
	<%= day_name(wday).first %>
305
	<%=  "#{dt.day}<br>"if @gantt.zoom == 4 %><%= day_name(wday).first %>
158 306
	</div>
159 307
	<% 
160 308
	left = left + width+1
161 309
	wday = wday + 1
310
        dt = dt + 1
162 311
	wday = 1 if wday > 7
163 312
	end
164 313
end %>
......
169 318
#
170 319
top = headers_height + 10
171 320
@gantt.events.each do |i| 
172
  if i.is_a? Issue 
173
	i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
174
	i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
175
	
176
	i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
177
	i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
178
	i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
179
	
180
	i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
181
	
182
	i_left = ((i_start_date - @gantt.date_from)*zoom).floor 	
183
	i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2                  # total width of the issue (- 2 for left and right borders)
184
	d_width = ((i_done_date - i_start_date)*zoom).floor - 2                     # done width
185
	l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
186
  css = "task " + (i.leaf? ? 'leaf' : 'parent')
321
  if i.is_a? Issue
322
    i_left, i_width, l_width, d_width = get_position(i, @gantt.date_from, @gantt.date_to, zoom)
187 323
	%>
188
	<div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="<%= css %> task_todo"><div class="left"></div>&nbsp;<div class="right"></div></div>
189
	<% if l_width > 0 %>
190
	    <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="<%= css %> task_late">&nbsp;</div>
191
	<% end %>
192
	<% if d_width > 0 %>
193
	    <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="<%= css %> task_done">&nbsp;</div>
194
	<% end %>
195
	<div style="top:<%= top %>px;left:<%= i_left + i_width + 8 %>px;background:#fff;" class="<%= css %>">
196
	<%= i.status.name %>
197
	<%= (i.done_ratio).to_i %>%
198
	</div>
199
	<div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
200
	<span class="tip">
324
  <div id="ev_<%=i.id%>" style="position:absolute;left:<%= i_left %>px;top:<%= top %>px;padding-top:3px;height:18px;width:<%= i_width+100 %>px;" class="handle">
325
    <div id="task_todo_<%=i.id%>" style="float:left:0px; width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
326
    <div id="task_late_<%=i.id%>" style="float:left:0px; width:<%= l_width %>px;" class="<%= l_width == 0 ? 'task task_none' : 'task task_late' %>">&nbsp;</div>
327
    <div id="task_done_<%=i.id%>" style="float:left:0px; width:<%= d_width %>px;" class="<%= d_width == 0 ? 'task task_none' : 'task task_done' %>">&nbsp;</div>
328
    <div id="task_task_<%=i.id%>" style="position:absolute;left:<%= (i_width + 5) %>px;background:#fff;" class="task">
329
    <%= h(i.status.name) %>
330
    <%= (i.done_ratio).to_i %>%
331
    <% if !i.due_date && i.fixed_version %>
332
      -&nbsp;<strong><%= h(i.fixed_version.name)  %></strong>
333
    <% end %>
334
    </div>
335
  </div>
336
	<%# === tooltip === %>
337
	<div id="tt_<%=i.id%>" class="tooltip" style="position: absolute;top:<%= top+14 %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:8px;">
338
	<span class="tip" style="position: relative;top:-2px;">
201 339
    <%= render_issue_tooltip i %>
202 340
	</span></div>
341
  <%= draggable_element("ev_#{i.id}",
342
    :revert =>false, :scroll=>"'gantt-container'", :constraint => "'horizontal'", :snap=>zoom,
343
       :onEnd=>"function( draggable, event )  {issue_moved(draggable.element);}"
344
    ) %>
203 345
<% else 
204 346
    i_left = ((i.start_date - @gantt.date_from)*zoom).floor
205 347
    %>
config/routes.rb (working copy)
111 111
      issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
112 112
      issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
113 113
      issues_views.connect 'projects/:project_id/issues/gantt', :controller => 'gantts', :action => 'show'
114
      issues_views.connect 'projects/:project_id/issues/edit_gantt/:id', :controller => 'gantts', :action => 'edit_gantt', :id => /\d+/
114 115
      issues_views.connect 'projects/:project_id/issues/calendar', :controller => 'calendars', :action => 'show'
115 116
      issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
116 117
      issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
......
122 123
      issues_actions.connect 'issues', :action => 'index'
123 124
      issues_actions.connect 'projects/:project_id/issues', :action => 'create'
124 125
      issues_actions.connect 'projects/:project_id/issues/gantt', :controller => 'gantts', :action => 'show'
126
      issues_actions.connect 'projects/:project_id/issues/edit_gantt:id', :controller => 'gantts', :action => 'edit_gantt', :id => /\d+/
125 127
      issues_actions.connect 'projects/:project_id/issues/calendar', :controller => 'calendars', :action => 'show'
126 128
      issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
127 129
      issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
......
135 137
      issues_actions.connect 'issues/:id.:format', :action => 'destroy', :id => /\d+/, :format => /xml/
136 138
    end
137 139
    issues_routes.connect 'issues/gantt', :controller => 'gantts', :action => 'show'
140
    issues_routes.connect 'issues/edit_gantt/:id', :controller => 'gantts', :action => 'edit_gantt', :id => /\d+/
138 141
    issues_routes.connect 'issues/calendar', :controller => 'calendars', :action => 'show'
139 142
    issues_routes.connect 'issues/:action'
140 143
  end
public/stylesheets/application.css (working copy)
788 788
.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
789 789
.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }  
790 790
.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
791
.task_none { background:transparent; border-style: none; }
791 792

  
792 793
.task_todo.parent { background: #888; border: 1px solid #888; height: 6px;}
793 794
.task_late.parent, .task_done.parent { height: 3px;}
(11-11/35)