##// END OF EJS Templates
Various timelog report enhancements:...
Jean-Philippe Lang -
r1311:4e961f44eff2
parent child
Show More
@@ -1,245 +1,242
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :issues
20 menu_item :issues
21 before_filter :find_project, :authorize
21 before_filter :find_project, :authorize
22
22
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
24
24
25 helper :sort
25 helper :sort
26 include SortHelper
26 include SortHelper
27 helper :issues
27 helper :issues
28 include TimelogHelper
28 include TimelogHelper
29
29
30 def report
30 def report
31 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
31 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
32 :klass => Project,
32 :klass => Project,
33 :label => :label_project},
33 :label => :label_project},
34 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
34 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
35 :klass => Version,
35 :klass => Version,
36 :label => :label_version},
36 :label => :label_version},
37 'category' => {:sql => "#{Issue.table_name}.category_id",
37 'category' => {:sql => "#{Issue.table_name}.category_id",
38 :klass => IssueCategory,
38 :klass => IssueCategory,
39 :label => :field_category},
39 :label => :field_category},
40 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
40 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
41 :klass => User,
41 :klass => User,
42 :label => :label_member},
42 :label => :label_member},
43 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
43 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
44 :klass => Tracker,
44 :klass => Tracker,
45 :label => :label_tracker},
45 :label => :label_tracker},
46 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
46 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
47 :klass => Enumeration,
47 :klass => Enumeration,
48 :label => :label_activity},
48 :label => :label_activity},
49 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
49 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
50 :klass => Issue,
50 :klass => Issue,
51 :label => :label_issue}
51 :label => :label_issue}
52 }
52 }
53
53
54 @criterias = params[:criterias] || []
54 @criterias = params[:criterias] || []
55 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
55 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
56 @criterias.uniq!
56 @criterias.uniq!
57 @criterias = @criterias[0,3]
57 @criterias = @criterias[0,3]
58
58
59 @columns = (params[:columns] && %w(year month week).include?(params[:columns])) ? params[:columns] : 'month'
59 @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
60
60
61 retrieve_date_range
61 retrieve_date_range
62 @from ||= TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today
63 @to ||= TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today
64
62
65 unless @criterias.empty?
63 unless @criterias.empty?
66 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
64 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
67 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
65 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
68
66
69 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
67 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
70 sql << " FROM #{TimeEntry.table_name}"
68 sql << " FROM #{TimeEntry.table_name}"
71 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
69 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
72 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
70 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
73 sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?)
71 sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?)
74 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
72 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
75 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
73 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
76 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
74 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
77
75
78 @hours = ActiveRecord::Base.connection.select_all(sql)
76 @hours = ActiveRecord::Base.connection.select_all(sql)
79
77
80 @hours.each do |row|
78 @hours.each do |row|
81 case @columns
79 case @columns
82 when 'year'
80 when 'year'
83 row['year'] = row['tyear']
81 row['year'] = row['tyear']
84 when 'month'
82 when 'month'
85 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
83 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
86 when 'week'
84 when 'week'
87 row['week'] = "#{row['tyear']}-#{row['tweek']}"
85 row['week'] = "#{row['tyear']}-#{row['tweek']}"
86 when 'day'
87 row['day'] = "#{row['spent_on']}"
88 end
88 end
89 end
89 end
90
90
91 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
91 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
92
92
93 @periods = []
93 @periods = []
94 # Date#at_beginning_of_ not supported in Rails 1.2.x
94 # Date#at_beginning_of_ not supported in Rails 1.2.x
95 date_from = @from.to_time
95 date_from = @from.to_time
96 # 100 columns max
96 # 100 columns max
97 while date_from <= @to.to_time && @periods.length < 100
97 while date_from <= @to.to_time && @periods.length < 100
98 case @columns
98 case @columns
99 when 'year'
99 when 'year'
100 @periods << "#{date_from.year}"
100 @periods << "#{date_from.year}"
101 date_from = (date_from + 1.year).at_beginning_of_year
101 date_from = (date_from + 1.year).at_beginning_of_year
102 when 'month'
102 when 'month'
103 @periods << "#{date_from.year}-#{date_from.month}"
103 @periods << "#{date_from.year}-#{date_from.month}"
104 date_from = (date_from + 1.month).at_beginning_of_month
104 date_from = (date_from + 1.month).at_beginning_of_month
105 when 'week'
105 when 'week'
106 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
106 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
107 date_from = (date_from + 7.day).at_beginning_of_week
107 date_from = (date_from + 7.day).at_beginning_of_week
108 when 'day'
109 @periods << "#{date_from.to_date}"
110 date_from = date_from + 1.day
108 end
111 end
109 end
112 end
110 end
113 end
111
114
112 render :layout => false if request.xhr?
115 render :layout => false if request.xhr?
113 end
116 end
114
117
115 def details
118 def details
116 sort_init 'spent_on', 'desc'
119 sort_init 'spent_on', 'desc'
117 sort_update
120 sort_update
118
121
119 cond = ARCondition.new
122 cond = ARCondition.new
120 cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) :
123 cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) :
121 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
124 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
122
125
123 retrieve_date_range
126 retrieve_date_range
124
127 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
125 if @from
126 if @to
127 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
128 else
129 cond << ['spent_on >= ?', @from]
130 end
131 elsif @to
132 cond << ['spent_on <= ?', @to]
133 end
134
128
135 TimeEntry.visible_by(User.current) do
129 TimeEntry.visible_by(User.current) do
136 respond_to do |format|
130 respond_to do |format|
137 format.html {
131 format.html {
138 # Paginate results
132 # Paginate results
139 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
133 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
140 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
134 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
141 @entries = TimeEntry.find(:all,
135 @entries = TimeEntry.find(:all,
142 :include => [:project, :activity, :user, {:issue => :tracker}],
136 :include => [:project, :activity, :user, {:issue => :tracker}],
143 :conditions => cond.conditions,
137 :conditions => cond.conditions,
144 :order => sort_clause,
138 :order => sort_clause,
145 :limit => @entry_pages.items_per_page,
139 :limit => @entry_pages.items_per_page,
146 :offset => @entry_pages.current.offset)
140 :offset => @entry_pages.current.offset)
147 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
141 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
142
148 render :layout => !request.xhr?
143 render :layout => !request.xhr?
149 }
144 }
150 format.csv {
145 format.csv {
151 # Export all entries
146 # Export all entries
152 @entries = TimeEntry.find(:all,
147 @entries = TimeEntry.find(:all,
153 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
148 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
154 :conditions => cond.conditions,
149 :conditions => cond.conditions,
155 :order => sort_clause)
150 :order => sort_clause)
156 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
151 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
157 }
152 }
158 end
153 end
159 end
154 end
160 end
155 end
161
156
162 def edit
157 def edit
163 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
158 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
164 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
159 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
165 @time_entry.attributes = params[:time_entry]
160 @time_entry.attributes = params[:time_entry]
166 if request.post? and @time_entry.save
161 if request.post? and @time_entry.save
167 flash[:notice] = l(:notice_successful_update)
162 flash[:notice] = l(:notice_successful_update)
168 redirect_to :action => 'details', :project_id => @time_entry.project
163 redirect_to :action => 'details', :project_id => @time_entry.project
169 return
164 return
170 end
165 end
171 @activities = Enumeration::get_values('ACTI')
166 @activities = Enumeration::get_values('ACTI')
172 end
167 end
173
168
174 def destroy
169 def destroy
175 render_404 and return unless @time_entry
170 render_404 and return unless @time_entry
176 render_403 and return unless @time_entry.editable_by?(User.current)
171 render_403 and return unless @time_entry.editable_by?(User.current)
177 @time_entry.destroy
172 @time_entry.destroy
178 flash[:notice] = l(:notice_successful_delete)
173 flash[:notice] = l(:notice_successful_delete)
179 redirect_to :back
174 redirect_to :back
180 rescue RedirectBackError
175 rescue RedirectBackError
181 redirect_to :action => 'details', :project_id => @time_entry.project
176 redirect_to :action => 'details', :project_id => @time_entry.project
182 end
177 end
183
178
184 private
179 private
185 def find_project
180 def find_project
186 if params[:id]
181 if params[:id]
187 @time_entry = TimeEntry.find(params[:id])
182 @time_entry = TimeEntry.find(params[:id])
188 @project = @time_entry.project
183 @project = @time_entry.project
189 elsif params[:issue_id]
184 elsif params[:issue_id]
190 @issue = Issue.find(params[:issue_id])
185 @issue = Issue.find(params[:issue_id])
191 @project = @issue.project
186 @project = @issue.project
192 elsif params[:project_id]
187 elsif params[:project_id]
193 @project = Project.find(params[:project_id])
188 @project = Project.find(params[:project_id])
194 else
189 else
195 render_404
190 render_404
196 return false
191 return false
197 end
192 end
198 rescue ActiveRecord::RecordNotFound
193 rescue ActiveRecord::RecordNotFound
199 render_404
194 render_404
200 end
195 end
201
196
202 # Retrieves the date range based on predefined ranges or specific from/to param dates
197 # Retrieves the date range based on predefined ranges or specific from/to param dates
203 def retrieve_date_range
198 def retrieve_date_range
204 @free_period = false
199 @free_period = false
205 @from, @to = nil, nil
200 @from, @to = nil, nil
206
201
207 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
202 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
208 case params[:period].to_s
203 case params[:period].to_s
209 when 'today'
204 when 'today'
210 @from = @to = Date.today
205 @from = @to = Date.today
211 when 'yesterday'
206 when 'yesterday'
212 @from = @to = Date.today - 1
207 @from = @to = Date.today - 1
213 when 'current_week'
208 when 'current_week'
214 @from = Date.today - (Date.today.cwday - 1)%7
209 @from = Date.today - (Date.today.cwday - 1)%7
215 @to = @from + 6
210 @to = @from + 6
216 when 'last_week'
211 when 'last_week'
217 @from = Date.today - 7 - (Date.today.cwday - 1)%7
212 @from = Date.today - 7 - (Date.today.cwday - 1)%7
218 @to = @from + 6
213 @to = @from + 6
219 when '7_days'
214 when '7_days'
220 @from = Date.today - 7
215 @from = Date.today - 7
221 @to = Date.today
216 @to = Date.today
222 when 'current_month'
217 when 'current_month'
223 @from = Date.civil(Date.today.year, Date.today.month, 1)
218 @from = Date.civil(Date.today.year, Date.today.month, 1)
224 @to = (@from >> 1) - 1
219 @to = (@from >> 1) - 1
225 when 'last_month'
220 when 'last_month'
226 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
221 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
227 @to = (@from >> 1) - 1
222 @to = (@from >> 1) - 1
228 when '30_days'
223 when '30_days'
229 @from = Date.today - 30
224 @from = Date.today - 30
230 @to = Date.today
225 @to = Date.today
231 when 'current_year'
226 when 'current_year'
232 @from = Date.civil(Date.today.year, 1, 1)
227 @from = Date.civil(Date.today.year, 1, 1)
233 @to = Date.civil(Date.today.year, 12, 31)
228 @to = Date.civil(Date.today.year, 12, 31)
234 end
229 end
235 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
230 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
236 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
231 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
237 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
232 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
238 @free_period = true
233 @free_period = true
239 else
234 else
240 # default
235 # default
241 end
236 end
242
237
243 @from, @to = @to, @from if @from && @to && @from > @to
238 @from, @to = @to, @from if @from && @to && @from > @to
239 @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1
240 @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today)
244 end
241 end
245 end
242 end
@@ -1,16 +1,26
1 <fieldset><legend><%= l(:label_date_range) %></legend>
1 <fieldset><legend><%= l(:label_date_range) %></legend>
2 <p>
2 <p>
3 <%= radio_button_tag 'period_type', '1', !@free_period %>
3 <%= radio_button_tag 'period_type', '1', !@free_period %>
4 <%= select_tag 'period', options_for_period_select(params[:period]),
4 <%= select_tag 'period', options_for_period_select(params[:period]),
5 :onchange => 'this.form.onsubmit();',
5 :onchange => 'this.form.onsubmit();',
6 :onfocus => '$("period_type_1").checked = true;' %>
6 :onfocus => '$("period_type_1").checked = true;' %>
7 </p>
7 </p>
8 <p>
8 <p>
9 <%= radio_button_tag 'period_type', '2', @free_period %>
9 <%= radio_button_tag 'period_type', '2', @free_period %>
10 <%= l(:label_date_from) %>
10 <%= l(:label_date_from) %>
11 <%= text_field_tag 'from', @from, :size => 10, :onfocus => '$("period_type_2").checked = true;' %> <%= calendar_for('from') %>
11 <%= text_field_tag 'from', @from, :size => 10, :onfocus => '$("period_type_2").checked = true;' %> <%= calendar_for('from') %>
12 <%= l(:label_date_to) %>
12 <%= l(:label_date_to) %>
13 <%= text_field_tag 'to', @to, :size => 10, :onfocus => '$("period_type_2").checked = true;' %> <%= calendar_for('to') %>
13 <%= text_field_tag 'to', @to, :size => 10, :onfocus => '$("period_type_2").checked = true;' %> <%= calendar_for('to') %>
14 <%= submit_tag l(:button_apply), :name => nil, :onclick => '$("period_type_2").checked = true;' %>
14 <%= submit_tag l(:button_apply), :name => nil, :onclick => '$("period_type_2").checked = true;' %>
15 </p>
15 </p>
16 </fieldset>
16 </fieldset>
17
18 <div class="tabs">
19 <% url_params = @free_period ? { :from => @from, :to => @to } : { :period => params[:period] } %>
20 <ul>
21 <li><%= link_to(l(:label_details), url_params.merge({:controller => 'timelog', :action => 'details', :project_id => @project }),
22 :class => (@controller.action_name == 'details' ? 'selected' : nil)) %></li>
23 <li><%= link_to(l(:label_report), url_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project}),
24 :class => (@controller.action_name == 'report' ? 'selected' : nil)) %></li>
25 </ul>
26 </div>
@@ -1,17 +1,19
1 <% @hours.collect {|h| h[criterias[level]]}.uniq.each do |value| %>
1 <% @hours.collect {|h| h[criterias[level]]}.uniq.each do |value| %>
2 <% hours_for_value = select_hours(hours, criterias[level], value) -%>
2 <% hours_for_value = select_hours(hours, criterias[level], value) -%>
3 <% next if hours_for_value.empty? -%>
3 <% next if hours_for_value.empty? -%>
4 <tr class="<%= cycle('odd', 'even') %> <%= 'last-level' unless criterias.length > level+1 %>">
4 <tr class="<%= cycle('odd', 'even') %> <%= 'last-level' unless criterias.length > level+1 %>">
5 <%= '<td></td>' * level %>
5 <%= '<td></td>' * level %>
6 <td><%= value.nil? ? l(:label_none) : @available_criterias[criterias[level]][:klass].find_by_id(value) %></td>
6 <td><%= value.nil? ? l(:label_none) : @available_criterias[criterias[level]][:klass].find_by_id(value) %></td>
7 <%= '<td></td>' * (criterias.length - level - 1) -%>
7 <%= '<td></td>' * (criterias.length - level - 1) -%>
8 <% total = 0 -%>
8 <% @periods.each do |period| -%>
9 <% @periods.each do |period| -%>
9 <% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)) %>
10 <% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)); total += sum -%>
10 <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td>
11 <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td>
11 <% end -%>
12 <% end -%>
13 <td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td>
12 </tr>
14 </tr>
13 <% if criterias.length > level+1 -%>
15 <% if criterias.length > level+1 -%>
14 <%= render(:partial => 'report_criteria', :locals => {:criterias => criterias, :hours => hours_for_value, :level => (level + 1)}) %>
16 <%= render(:partial => 'report_criteria', :locals => {:criterias => criterias, :hours => hours_for_value, :level => (level + 1)}) %>
15 <% end -%>
17 <% end -%>
16
18
17 <% end %>
19 <% end %>
@@ -1,32 +1,31
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}, :class => 'icon icon-report') %>
3 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
4 </div>
3 </div>
5
4
6 <h2><%= l(:label_spent_time) %></h2>
5 <h2><%= l(:label_spent_time) %></h2>
7
6
8 <% if @issue %>
7 <% if @issue %>
9 <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) %> / <%= link_to_issue(@issue) %></h3>
8 <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) %> / <%= link_to_issue(@issue) %></h3>
10 <% end %>
9 <% end %>
11
10
12 <% form_remote_tag( :url => {}, :method => :get, :update => 'content' ) do %>
11 <% form_remote_tag( :url => {}, :method => :get, :update => 'content' ) do %>
13 <%= hidden_field_tag 'project_id', params[:project_id] %>
12 <%= hidden_field_tag 'project_id', params[:project_id] %>
14 <%= hidden_field_tag 'issue_id', params[:issue_id] if @issue %>
13 <%= hidden_field_tag 'issue_id', params[:issue_id] if @issue %>
15 <%= render :partial => 'date_range' %>
14 <%= render :partial => 'date_range' %>
16 <% end %>
15 <% end %>
17
16
18 <div class="total-hours">
17 <div class="total-hours">
19 <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p>
18 <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p>
20 </div>
19 </div>
21
20
22 <% unless @entries.empty? %>
21 <% unless @entries.empty? %>
23 <%= render :partial => 'list', :locals => { :entries => @entries }%>
22 <%= render :partial => 'list', :locals => { :entries => @entries }%>
24 <p class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></p>
23 <p class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></p>
25
24
26 <p class="other-formats">
25 <p class="other-formats">
27 <%= l(:label_export_to) %>
26 <%= l(:label_export_to) %>
28 <span><%= link_to 'CSV', params.merge(:format => 'csv'), :class => 'csv' %></span>
27 <span><%= link_to 'CSV', params.merge(:format => 'csv'), :class => 'csv' %></span>
29 </p>
28 </p>
30 <% end %>
29 <% end %>
31
30
32 <% html_title l(:label_spent_time), l(:label_details) %>
31 <% html_title l(:label_spent_time), l(:label_details) %>
@@ -1,62 +1,67
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}, :class => 'icon icon-details') %>
3 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
4 </div>
3 </div>
5
4
6 <h2><%= l(:label_spent_time) %></h2>
5 <h2><%= l(:label_spent_time) %></h2>
7
6
8 <% form_remote_tag(:url => {}, :update => 'content') do %>
7 <% form_remote_tag(:url => {}, :update => 'content') do %>
9 <% @criterias.each do |criteria| %>
8 <% @criterias.each do |criteria| %>
10 <%= hidden_field_tag 'criterias[]', criteria, :id => nil %>
9 <%= hidden_field_tag 'criterias[]', criteria, :id => nil %>
11 <% end %>
10 <% end %>
12 <%= hidden_field_tag 'project_id', params[:project_id] %>
11 <%= hidden_field_tag 'project_id', params[:project_id] %>
13 <%= render :partial => 'date_range' %>
12 <%= render :partial => 'date_range' %>
14
13
15 <p><%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
14 <p><%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
16 [l(:label_month), 'month'],
15 [l(:label_month), 'month'],
17 [l(:label_week), 'week']], @columns),
16 [l(:label_week), 'week'],
17 [l(:label_day_plural).titleize, 'day']], @columns),
18 :onchange => "this.form.onsubmit();" %>
18 :onchange => "this.form.onsubmit();" %>
19
19
20 <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l(@available_criterias[k][:label]), k]}),
20 <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l(@available_criterias[k][:label]), k]}),
21 :onchange => "this.form.onsubmit();",
21 :onchange => "this.form.onsubmit();",
22 :style => 'width: 200px',
22 :style => 'width: 200px',
23 :id => nil,
23 :id => nil,
24 :disabled => (@criterias.length >= 3)) %>
24 :disabled => (@criterias.length >= 3)) %>
25 <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :date_from => @date_from, :date_to => @date_to, :period => @columns}, :update => 'content'},
25 <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns},
26 :class => 'icon icon-reload' %></p>
26 :update => 'content'
27 }, :class => 'icon icon-reload' %></p>
27 <% end %>
28 <% end %>
28
29
29 <% unless @criterias.empty? %>
30 <% unless @criterias.empty? %>
30 <div class="total-hours">
31 <div class="total-hours">
31 <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p>
32 <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p>
32 </div>
33 </div>
33
34
34 <% unless @hours.empty? %>
35 <% unless @hours.empty? %>
35 <table class="list" id="time-report">
36 <table class="list" id="time-report">
36 <thead>
37 <thead>
37 <tr>
38 <tr>
38 <% @criterias.each do |criteria| %>
39 <% @criterias.each do |criteria| %>
39 <th><%= l(@available_criterias[criteria][:label]) %></th>
40 <th><%= l(@available_criterias[criteria][:label]) %></th>
40 <% end %>
41 <% end %>
42 <% columns_width = (40 / (@periods.length+1)).to_i %>
41 <% @periods.each do |period| %>
43 <% @periods.each do |period| %>
42 <th class="period" width="<%= (40 / @periods.length).to_i %>%"><%= period %></th>
44 <th class="period" width="<%= columns_width %>%"><%= period %></th>
43 <% end %>
45 <% end %>
46 <th class="total" width="<%= columns_width %>%"><%= l(:label_total) %></th>
44 </tr>
47 </tr>
45 </thead>
48 </thead>
46 <tbody>
49 <tbody>
47 <%= render :partial => 'report_criteria', :locals => {:criterias => @criterias, :hours => @hours, :level => 0} %>
50 <%= render :partial => 'report_criteria', :locals => {:criterias => @criterias, :hours => @hours, :level => 0} %>
48 <tr class="total">
51 <tr class="total">
49 <td><%= l(:label_total) %></td>
52 <td><%= l(:label_total) %></td>
50 <%= '<td></td>' * (@criterias.size - 1) %>
53 <%= '<td></td>' * (@criterias.size - 1) %>
54 <% total = 0 -%>
51 <% @periods.each do |period| -%>
55 <% @periods.each do |period| -%>
52 <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)) %>
56 <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%>
53 <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td>
57 <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td>
54 <% end -%>
58 <% end -%>
59 <td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td>
55 </tr>
60 </tr>
56 </tbody>
61 </tbody>
57 </table>
62 </table>
58 <% end %>
63 <% end %>
59 <% end %>
64 <% end %>
60
65
61 <% html_title l(:label_spent_time), l(:label_report) %>
66 <% html_title l(:label_spent_time), l(:label_report) %>
62
67
@@ -1,578 +1,578
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #wrapper {background: white;}
10 #wrapper {background: white;}
11
11
12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 #top-menu ul {margin: 0; padding: 0;}
13 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu li {
14 #top-menu li {
15 float:left;
15 float:left;
16 list-style-type:none;
16 list-style-type:none;
17 margin: 0px 0px 0px 0px;
17 margin: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
19 white-space:nowrap;
19 white-space:nowrap;
20 }
20 }
21 #top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
21 #top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23
23
24 #account {float:right;}
24 #account {float:right;}
25
25
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header a {color:#f8f8f8;}
27 #header a {color:#f8f8f8;}
28 #quick-search {float:right;}
28 #quick-search {float:right;}
29
29
30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
31 #main-menu ul {margin: 0; padding: 0;}
31 #main-menu ul {margin: 0; padding: 0;}
32 #main-menu li {
32 #main-menu li {
33 float:left;
33 float:left;
34 list-style-type:none;
34 list-style-type:none;
35 margin: 0px 2px 0px 0px;
35 margin: 0px 2px 0px 0px;
36 padding: 0px 0px 0px 0px;
36 padding: 0px 0px 0px 0px;
37 white-space:nowrap;
37 white-space:nowrap;
38 }
38 }
39 #main-menu li a {
39 #main-menu li a {
40 display: block;
40 display: block;
41 color: #fff;
41 color: #fff;
42 text-decoration: none;
42 text-decoration: none;
43 font-weight: bold;
43 font-weight: bold;
44 margin: 0;
44 margin: 0;
45 padding: 4px 10px 4px 10px;
45 padding: 4px 10px 4px 10px;
46 }
46 }
47 #main-menu li a:hover {background:#759FCF; color:#fff;}
47 #main-menu li a:hover {background:#759FCF; color:#fff;}
48 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
48 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
49
49
50 #main {background-color:#EEEEEE;}
50 #main {background-color:#EEEEEE;}
51
51
52 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
52 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
53 * html #sidebar{ width: 17%; }
53 * html #sidebar{ width: 17%; }
54 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
54 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
55 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
55 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
56 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
56 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
57
57
58 #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
58 #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
59 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
59 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
60 html>body #content { height: auto; min-height: 600px; overflow: auto; }
60 html>body #content { height: auto; min-height: 600px; overflow: auto; }
61
61
62 #main.nosidebar #sidebar{ display: none; }
62 #main.nosidebar #sidebar{ display: none; }
63 #main.nosidebar #content{ width: auto; border-right: 0; }
63 #main.nosidebar #content{ width: auto; border-right: 0; }
64
64
65 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
65 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
66
66
67 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
67 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
68 #login-form table td {padding: 6px;}
68 #login-form table td {padding: 6px;}
69 #login-form label {font-weight: bold;}
69 #login-form label {font-weight: bold;}
70
70
71 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
71 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
72
72
73 /***** Links *****/
73 /***** Links *****/
74 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
74 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
75 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
75 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
76 a img{ border: 0; }
76 a img{ border: 0; }
77
77
78 a.issue.closed, .issue.closed a { text-decoration: line-through; }
78 a.issue.closed, .issue.closed a { text-decoration: line-through; }
79
79
80 /***** Tables *****/
80 /***** Tables *****/
81 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
81 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
82 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
82 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
83 table.list td { overflow: hidden; vertical-align: top;}
83 table.list td { overflow: hidden; vertical-align: top;}
84 table.list td.id { width: 2%; text-align: center;}
84 table.list td.id { width: 2%; text-align: center;}
85 table.list td.checkbox { width: 15px; padding: 0px;}
85 table.list td.checkbox { width: 15px; padding: 0px;}
86
86
87 table.list.issues { margin-top: 10px; }
87 table.list.issues { margin-top: 10px; }
88 tr.issue { text-align: center; white-space: nowrap; }
88 tr.issue { text-align: center; white-space: nowrap; }
89 tr.issue td.subject, tr.issue td.category { white-space: normal; }
89 tr.issue td.subject, tr.issue td.category { white-space: normal; }
90 tr.issue td.subject { text-align: left; }
90 tr.issue td.subject { text-align: left; }
91 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
91 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
92
92
93 tr.entry { border: 1px solid #f8f8f8; }
93 tr.entry { border: 1px solid #f8f8f8; }
94 tr.entry td { white-space: nowrap; }
94 tr.entry td { white-space: nowrap; }
95 tr.entry td.filename { width: 30%; }
95 tr.entry td.filename { width: 30%; }
96 tr.entry td.size { text-align: right; font-size: 90%; }
96 tr.entry td.size { text-align: right; font-size: 90%; }
97 tr.entry td.revision, tr.entry td.author { text-align: center; }
97 tr.entry td.revision, tr.entry td.author { text-align: center; }
98 tr.entry td.age { text-align: right; }
98 tr.entry td.age { text-align: right; }
99
99
100 tr.changeset td.author { text-align: center; width: 15%; }
100 tr.changeset td.author { text-align: center; width: 15%; }
101 tr.changeset td.committed_on { text-align: center; width: 15%; }
101 tr.changeset td.committed_on { text-align: center; width: 15%; }
102
102
103 tr.message { height: 2.6em; }
103 tr.message { height: 2.6em; }
104 tr.message td.last_message { font-size: 80%; }
104 tr.message td.last_message { font-size: 80%; }
105 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
105 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
106 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
106 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
107
107
108 tr.user td { width:13%; }
108 tr.user td { width:13%; }
109 tr.user td.email { width:18%; }
109 tr.user td.email { width:18%; }
110 tr.user td { white-space: nowrap; }
110 tr.user td { white-space: nowrap; }
111 tr.user.locked, tr.user.registered { color: #aaa; }
111 tr.user.locked, tr.user.registered { color: #aaa; }
112 tr.user.locked a, tr.user.registered a { color: #aaa; }
112 tr.user.locked a, tr.user.registered a { color: #aaa; }
113
113
114 tr.time-entry { text-align: center; white-space: nowrap; }
114 tr.time-entry { text-align: center; white-space: nowrap; }
115 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
115 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
116 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
116 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
117 td.hours .hours-dec { font-size: 0.9em; }
117 td.hours .hours-dec { font-size: 0.9em; }
118
118
119 table.list tbody tr:hover { background-color:#ffffdd; }
119 table.list tbody tr:hover { background-color:#ffffdd; }
120 table td {padding:2px;}
120 table td {padding:2px;}
121 table p {margin:0;}
121 table p {margin:0;}
122 .odd {background-color:#f6f7f8;}
122 .odd {background-color:#f6f7f8;}
123 .even {background-color: #fff;}
123 .even {background-color: #fff;}
124
124
125 .highlight { background-color: #FCFD8D;}
125 .highlight { background-color: #FCFD8D;}
126 .highlight.token-1 { background-color: #faa;}
126 .highlight.token-1 { background-color: #faa;}
127 .highlight.token-2 { background-color: #afa;}
127 .highlight.token-2 { background-color: #afa;}
128 .highlight.token-3 { background-color: #aaf;}
128 .highlight.token-3 { background-color: #aaf;}
129
129
130 .box{
130 .box{
131 padding:6px;
131 padding:6px;
132 margin-bottom: 10px;
132 margin-bottom: 10px;
133 background-color:#f6f6f6;
133 background-color:#f6f6f6;
134 color:#505050;
134 color:#505050;
135 line-height:1.5em;
135 line-height:1.5em;
136 border: 1px solid #e4e4e4;
136 border: 1px solid #e4e4e4;
137 }
137 }
138
138
139 div.square {
139 div.square {
140 border: 1px solid #999;
140 border: 1px solid #999;
141 float: left;
141 float: left;
142 margin: .3em .4em 0 .4em;
142 margin: .3em .4em 0 .4em;
143 overflow: hidden;
143 overflow: hidden;
144 width: .6em; height: .6em;
144 width: .6em; height: .6em;
145 }
145 }
146 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
146 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
147 .contextual input {font-size:0.9em;}
147 .contextual input {font-size:0.9em;}
148
148
149 .splitcontentleft{float:left; width:49%;}
149 .splitcontentleft{float:left; width:49%;}
150 .splitcontentright{float:right; width:49%;}
150 .splitcontentright{float:right; width:49%;}
151 form {display: inline;}
151 form {display: inline;}
152 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
152 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
153 fieldset {border: 1px solid #e4e4e4; margin:0;}
153 fieldset {border: 1px solid #e4e4e4; margin:0;}
154 legend {color: #484848;}
154 legend {color: #484848;}
155 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
155 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
156 textarea.wiki-edit { width: 99%; }
156 textarea.wiki-edit { width: 99%; }
157 li p {margin-top: 0;}
157 li p {margin-top: 0;}
158 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
158 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
159 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
159 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
160 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
160 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
161
161
162 fieldset#filters .buttons { text-align: right; font-size: 0.9em; margin: 0 4px 0px 0; }
162 fieldset#filters .buttons { text-align: right; font-size: 0.9em; margin: 0 4px 0px 0; }
163
163
164 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
164 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
165 div#issue-changesets .changeset { padding: 4px;}
165 div#issue-changesets .changeset { padding: 4px;}
166 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
166 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
167 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
167 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
168
168
169 div#activity dl { margin-left: 2em; }
169 div#activity dl { margin-left: 2em; }
170 div#activity dd { margin-bottom: 1em; }
170 div#activity dd { margin-bottom: 1em; }
171 div#activity dt { margin-bottom: 1px; }
171 div#activity dt { margin-bottom: 1px; }
172 div#activity dt .time { color: #777; font-size: 80%; }
172 div#activity dt .time { color: #777; font-size: 80%; }
173 div#activity dd .description { font-style: italic; }
173 div#activity dd .description { font-style: italic; }
174 div#activity span.project:after { content: " -"; }
174 div#activity span.project:after { content: " -"; }
175
175
176 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
176 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
177 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
177 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
178 div#roadmap .wiki h1:first-child { display: none; }
178 div#roadmap .wiki h1:first-child { display: none; }
179 div#roadmap .wiki h1 { font-size: 120%; }
179 div#roadmap .wiki h1 { font-size: 120%; }
180 div#roadmap .wiki h2 { font-size: 110%; }
180 div#roadmap .wiki h2 { font-size: 110%; }
181
181
182 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
182 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
183 div#version-summary fieldset { margin-bottom: 1em; }
183 div#version-summary fieldset { margin-bottom: 1em; }
184 div#version-summary .total-hours { text-align: right; }
184 div#version-summary .total-hours { text-align: right; }
185
185
186 table#time-report td.hours, table#time-report th.period { text-align: right; padding-right: 0.5em; }
186 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
187 table#time-report tbody tr { font-style: italic; color: #777; }
187 table#time-report tbody tr { font-style: italic; color: #777; }
188 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
188 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
189 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
189 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
190 table#time-report .hours-dec { font-size: 0.9em; }
190 table#time-report .hours-dec { font-size: 0.9em; }
191
191
192 .total-hours { font-size: 110%; font-weight: bold; }
192 .total-hours { font-size: 110%; font-weight: bold; }
193 .total-hours span.hours-int { font-size: 120%; }
193 .total-hours span.hours-int { font-size: 120%; }
194
194
195 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
195 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
196 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
196 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
197
197
198 .pagination {font-size: 90%}
198 .pagination {font-size: 90%}
199 p.pagination {margin-top:8px;}
199 p.pagination {margin-top:8px;}
200
200
201 /***** Tabular forms ******/
201 /***** Tabular forms ******/
202 .tabular p{
202 .tabular p{
203 margin: 0;
203 margin: 0;
204 padding: 5px 0 8px 0;
204 padding: 5px 0 8px 0;
205 padding-left: 180px; /*width of left column containing the label elements*/
205 padding-left: 180px; /*width of left column containing the label elements*/
206 height: 1%;
206 height: 1%;
207 clear:left;
207 clear:left;
208 }
208 }
209
209
210 .tabular label{
210 .tabular label{
211 font-weight: bold;
211 font-weight: bold;
212 float: left;
212 float: left;
213 text-align: right;
213 text-align: right;
214 margin-left: -180px; /*width of left column*/
214 margin-left: -180px; /*width of left column*/
215 width: 175px; /*width of labels. Should be smaller than left column to create some right
215 width: 175px; /*width of labels. Should be smaller than left column to create some right
216 margin*/
216 margin*/
217 }
217 }
218
218
219 .tabular label.floating{
219 .tabular label.floating{
220 font-weight: normal;
220 font-weight: normal;
221 margin-left: 0px;
221 margin-left: 0px;
222 text-align: left;
222 text-align: left;
223 width: 200px;
223 width: 200px;
224 }
224 }
225
225
226 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
226 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
227
227
228 .tabular.settings p{ padding-left: 300px; }
228 .tabular.settings p{ padding-left: 300px; }
229 .tabular.settings label{ margin-left: -300px; width: 295px; }
229 .tabular.settings label{ margin-left: -300px; width: 295px; }
230
230
231 .required {color: #bb0000;}
231 .required {color: #bb0000;}
232 .summary {font-style: italic;}
232 .summary {font-style: italic;}
233
233
234 #attachments_fields input[type=text] {margin-left: 8px; }
234 #attachments_fields input[type=text] {margin-left: 8px; }
235
235
236 div.attachments p { margin:4px 0 2px 0; }
236 div.attachments p { margin:4px 0 2px 0; }
237 div.attachments img { vertical-align: middle; }
237 div.attachments img { vertical-align: middle; }
238 div.attachments span.author { font-size: 0.9em; color: #888; }
238 div.attachments span.author { font-size: 0.9em; color: #888; }
239
239
240 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
240 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
241 .other-formats span + span:before { content: "| "; }
241 .other-formats span + span:before { content: "| "; }
242
242
243 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
243 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
244
244
245 /***** Flash & error messages ****/
245 /***** Flash & error messages ****/
246 #errorExplanation, div.flash, .nodata {
246 #errorExplanation, div.flash, .nodata {
247 padding: 4px 4px 4px 30px;
247 padding: 4px 4px 4px 30px;
248 margin-bottom: 12px;
248 margin-bottom: 12px;
249 font-size: 1.1em;
249 font-size: 1.1em;
250 border: 2px solid;
250 border: 2px solid;
251 }
251 }
252
252
253 div.flash {margin-top: 8px;}
253 div.flash {margin-top: 8px;}
254
254
255 div.flash.error, #errorExplanation {
255 div.flash.error, #errorExplanation {
256 background: url(../images/false.png) 8px 5px no-repeat;
256 background: url(../images/false.png) 8px 5px no-repeat;
257 background-color: #ffe3e3;
257 background-color: #ffe3e3;
258 border-color: #dd0000;
258 border-color: #dd0000;
259 color: #550000;
259 color: #550000;
260 }
260 }
261
261
262 div.flash.notice {
262 div.flash.notice {
263 background: url(../images/true.png) 8px 5px no-repeat;
263 background: url(../images/true.png) 8px 5px no-repeat;
264 background-color: #dfffdf;
264 background-color: #dfffdf;
265 border-color: #9fcf9f;
265 border-color: #9fcf9f;
266 color: #005f00;
266 color: #005f00;
267 }
267 }
268
268
269 .nodata {
269 .nodata {
270 text-align: center;
270 text-align: center;
271 background-color: #FFEBC1;
271 background-color: #FFEBC1;
272 border-color: #FDBF3B;
272 border-color: #FDBF3B;
273 color: #A6750C;
273 color: #A6750C;
274 }
274 }
275
275
276 #errorExplanation ul { font-size: 0.9em;}
276 #errorExplanation ul { font-size: 0.9em;}
277
277
278 /***** Ajax indicator ******/
278 /***** Ajax indicator ******/
279 #ajax-indicator {
279 #ajax-indicator {
280 position: absolute; /* fixed not supported by IE */
280 position: absolute; /* fixed not supported by IE */
281 background-color:#eee;
281 background-color:#eee;
282 border: 1px solid #bbb;
282 border: 1px solid #bbb;
283 top:35%;
283 top:35%;
284 left:40%;
284 left:40%;
285 width:20%;
285 width:20%;
286 font-weight:bold;
286 font-weight:bold;
287 text-align:center;
287 text-align:center;
288 padding:0.6em;
288 padding:0.6em;
289 z-index:100;
289 z-index:100;
290 filter:alpha(opacity=50);
290 filter:alpha(opacity=50);
291 opacity: 0.5;
291 opacity: 0.5;
292 }
292 }
293
293
294 html>body #ajax-indicator { position: fixed; }
294 html>body #ajax-indicator { position: fixed; }
295
295
296 #ajax-indicator span {
296 #ajax-indicator span {
297 background-position: 0% 40%;
297 background-position: 0% 40%;
298 background-repeat: no-repeat;
298 background-repeat: no-repeat;
299 background-image: url(../images/loading.gif);
299 background-image: url(../images/loading.gif);
300 padding-left: 26px;
300 padding-left: 26px;
301 vertical-align: bottom;
301 vertical-align: bottom;
302 }
302 }
303
303
304 /***** Calendar *****/
304 /***** Calendar *****/
305 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
305 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
306 table.cal thead th {width: 14%;}
306 table.cal thead th {width: 14%;}
307 table.cal tbody tr {height: 100px;}
307 table.cal tbody tr {height: 100px;}
308 table.cal th { background-color:#EEEEEE; padding: 4px; }
308 table.cal th { background-color:#EEEEEE; padding: 4px; }
309 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
309 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
310 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
310 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
311 table.cal td.odd p.day-num {color: #bbb;}
311 table.cal td.odd p.day-num {color: #bbb;}
312 table.cal td.today {background:#ffffdd;}
312 table.cal td.today {background:#ffffdd;}
313 table.cal td.today p.day-num {font-weight: bold;}
313 table.cal td.today p.day-num {font-weight: bold;}
314
314
315 /***** Tooltips ******/
315 /***** Tooltips ******/
316 .tooltip{position:relative;z-index:24;}
316 .tooltip{position:relative;z-index:24;}
317 .tooltip:hover{z-index:25;color:#000;}
317 .tooltip:hover{z-index:25;color:#000;}
318 .tooltip span.tip{display: none; text-align:left;}
318 .tooltip span.tip{display: none; text-align:left;}
319
319
320 div.tooltip:hover span.tip{
320 div.tooltip:hover span.tip{
321 display:block;
321 display:block;
322 position:absolute;
322 position:absolute;
323 top:12px; left:24px; width:270px;
323 top:12px; left:24px; width:270px;
324 border:1px solid #555;
324 border:1px solid #555;
325 background-color:#fff;
325 background-color:#fff;
326 padding: 4px;
326 padding: 4px;
327 font-size: 0.8em;
327 font-size: 0.8em;
328 color:#505050;
328 color:#505050;
329 }
329 }
330
330
331 /***** Progress bar *****/
331 /***** Progress bar *****/
332 table.progress {
332 table.progress {
333 border: 1px solid #D7D7D7;
333 border: 1px solid #D7D7D7;
334 border-collapse: collapse;
334 border-collapse: collapse;
335 border-spacing: 0pt;
335 border-spacing: 0pt;
336 empty-cells: show;
336 empty-cells: show;
337 text-align: center;
337 text-align: center;
338 float:left;
338 float:left;
339 margin: 1px 6px 1px 0px;
339 margin: 1px 6px 1px 0px;
340 }
340 }
341
341
342 table.progress td { height: 0.9em; }
342 table.progress td { height: 0.9em; }
343 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
343 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
344 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
344 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
345 table.progress td.open { background: #FFF none repeat scroll 0%; }
345 table.progress td.open { background: #FFF none repeat scroll 0%; }
346 p.pourcent {font-size: 80%;}
346 p.pourcent {font-size: 80%;}
347 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
347 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
348
348
349 /***** Tabs *****/
349 /***** Tabs *****/
350 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
350 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
351 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
351 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
352 #content .tabs>ul { bottom:-1px; } /* others */
352 #content .tabs>ul { bottom:-1px; } /* others */
353 #content .tabs ul li {
353 #content .tabs ul li {
354 float:left;
354 float:left;
355 list-style-type:none;
355 list-style-type:none;
356 white-space:nowrap;
356 white-space:nowrap;
357 margin-right:8px;
357 margin-right:8px;
358 background:#fff;
358 background:#fff;
359 }
359 }
360 #content .tabs ul li a{
360 #content .tabs ul li a{
361 display:block;
361 display:block;
362 font-size: 0.9em;
362 font-size: 0.9em;
363 text-decoration:none;
363 text-decoration:none;
364 line-height:1.3em;
364 line-height:1.3em;
365 padding:4px 6px 4px 6px;
365 padding:4px 6px 4px 6px;
366 border: 1px solid #ccc;
366 border: 1px solid #ccc;
367 border-bottom: 1px solid #bbbbbb;
367 border-bottom: 1px solid #bbbbbb;
368 background-color: #eeeeee;
368 background-color: #eeeeee;
369 color:#777;
369 color:#777;
370 font-weight:bold;
370 font-weight:bold;
371 }
371 }
372
372
373 #content .tabs ul li a:hover {
373 #content .tabs ul li a:hover {
374 background-color: #ffffdd;
374 background-color: #ffffdd;
375 text-decoration:none;
375 text-decoration:none;
376 }
376 }
377
377
378 #content .tabs ul li a.selected {
378 #content .tabs ul li a.selected {
379 background-color: #fff;
379 background-color: #fff;
380 border: 1px solid #bbbbbb;
380 border: 1px solid #bbbbbb;
381 border-bottom: 1px solid #fff;
381 border-bottom: 1px solid #fff;
382 }
382 }
383
383
384 #content .tabs ul li a.selected:hover {
384 #content .tabs ul li a.selected:hover {
385 background-color: #fff;
385 background-color: #fff;
386 }
386 }
387
387
388 /***** Diff *****/
388 /***** Diff *****/
389 .diff_out { background: #fcc; }
389 .diff_out { background: #fcc; }
390 .diff_in { background: #cfc; }
390 .diff_in { background: #cfc; }
391
391
392 /***** Wiki *****/
392 /***** Wiki *****/
393 div.wiki table {
393 div.wiki table {
394 border: 1px solid #505050;
394 border: 1px solid #505050;
395 border-collapse: collapse;
395 border-collapse: collapse;
396 margin-bottom: 1em;
396 margin-bottom: 1em;
397 }
397 }
398
398
399 div.wiki table, div.wiki td, div.wiki th {
399 div.wiki table, div.wiki td, div.wiki th {
400 border: 1px solid #bbb;
400 border: 1px solid #bbb;
401 padding: 4px;
401 padding: 4px;
402 }
402 }
403
403
404 div.wiki .external {
404 div.wiki .external {
405 background-position: 0% 60%;
405 background-position: 0% 60%;
406 background-repeat: no-repeat;
406 background-repeat: no-repeat;
407 padding-left: 12px;
407 padding-left: 12px;
408 background-image: url(../images/external.png);
408 background-image: url(../images/external.png);
409 }
409 }
410
410
411 div.wiki a.new {
411 div.wiki a.new {
412 color: #b73535;
412 color: #b73535;
413 }
413 }
414
414
415 div.wiki pre {
415 div.wiki pre {
416 margin: 1em 1em 1em 1.6em;
416 margin: 1em 1em 1em 1.6em;
417 padding: 2px;
417 padding: 2px;
418 background-color: #fafafa;
418 background-color: #fafafa;
419 border: 1px solid #dadada;
419 border: 1px solid #dadada;
420 width:95%;
420 width:95%;
421 overflow-x: auto;
421 overflow-x: auto;
422 }
422 }
423
423
424 div.wiki div.toc {
424 div.wiki div.toc {
425 background-color: #ffffdd;
425 background-color: #ffffdd;
426 border: 1px solid #e4e4e4;
426 border: 1px solid #e4e4e4;
427 padding: 4px;
427 padding: 4px;
428 line-height: 1.2em;
428 line-height: 1.2em;
429 margin-bottom: 12px;
429 margin-bottom: 12px;
430 margin-right: 12px;
430 margin-right: 12px;
431 display: table
431 display: table
432 }
432 }
433 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
433 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
434
434
435 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
435 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
436 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
436 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
437
437
438 div.wiki div.toc a {
438 div.wiki div.toc a {
439 display: block;
439 display: block;
440 font-size: 0.9em;
440 font-size: 0.9em;
441 font-weight: normal;
441 font-weight: normal;
442 text-decoration: none;
442 text-decoration: none;
443 color: #606060;
443 color: #606060;
444 }
444 }
445 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
445 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
446
446
447 div.wiki div.toc a.heading2 { margin-left: 6px; }
447 div.wiki div.toc a.heading2 { margin-left: 6px; }
448 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
448 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
449
449
450 /***** My page layout *****/
450 /***** My page layout *****/
451 .block-receiver {
451 .block-receiver {
452 border:1px dashed #c0c0c0;
452 border:1px dashed #c0c0c0;
453 margin-bottom: 20px;
453 margin-bottom: 20px;
454 padding: 15px 0 15px 0;
454 padding: 15px 0 15px 0;
455 }
455 }
456
456
457 .mypage-box {
457 .mypage-box {
458 margin:0 0 20px 0;
458 margin:0 0 20px 0;
459 color:#505050;
459 color:#505050;
460 line-height:1.5em;
460 line-height:1.5em;
461 }
461 }
462
462
463 .handle {
463 .handle {
464 cursor: move;
464 cursor: move;
465 }
465 }
466
466
467 a.close-icon {
467 a.close-icon {
468 display:block;
468 display:block;
469 margin-top:3px;
469 margin-top:3px;
470 overflow:hidden;
470 overflow:hidden;
471 width:12px;
471 width:12px;
472 height:12px;
472 height:12px;
473 background-repeat: no-repeat;
473 background-repeat: no-repeat;
474 cursor:pointer;
474 cursor:pointer;
475 background-image:url('../images/close.png');
475 background-image:url('../images/close.png');
476 }
476 }
477
477
478 a.close-icon:hover {
478 a.close-icon:hover {
479 background-image:url('../images/close_hl.png');
479 background-image:url('../images/close_hl.png');
480 }
480 }
481
481
482 /***** Gantt chart *****/
482 /***** Gantt chart *****/
483 .gantt_hdr {
483 .gantt_hdr {
484 position:absolute;
484 position:absolute;
485 top:0;
485 top:0;
486 height:16px;
486 height:16px;
487 border-top: 1px solid #c0c0c0;
487 border-top: 1px solid #c0c0c0;
488 border-bottom: 1px solid #c0c0c0;
488 border-bottom: 1px solid #c0c0c0;
489 border-right: 1px solid #c0c0c0;
489 border-right: 1px solid #c0c0c0;
490 text-align: center;
490 text-align: center;
491 overflow: hidden;
491 overflow: hidden;
492 }
492 }
493
493
494 .task {
494 .task {
495 position: absolute;
495 position: absolute;
496 height:8px;
496 height:8px;
497 font-size:0.8em;
497 font-size:0.8em;
498 color:#888;
498 color:#888;
499 padding:0;
499 padding:0;
500 margin:0;
500 margin:0;
501 line-height:0.8em;
501 line-height:0.8em;
502 }
502 }
503
503
504 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
504 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
505 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
505 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
506 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
506 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
507 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
507 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
508
508
509 /***** Icons *****/
509 /***** Icons *****/
510 .icon {
510 .icon {
511 background-position: 0% 40%;
511 background-position: 0% 40%;
512 background-repeat: no-repeat;
512 background-repeat: no-repeat;
513 padding-left: 20px;
513 padding-left: 20px;
514 padding-top: 2px;
514 padding-top: 2px;
515 padding-bottom: 3px;
515 padding-bottom: 3px;
516 }
516 }
517
517
518 .icon22 {
518 .icon22 {
519 background-position: 0% 40%;
519 background-position: 0% 40%;
520 background-repeat: no-repeat;
520 background-repeat: no-repeat;
521 padding-left: 26px;
521 padding-left: 26px;
522 line-height: 22px;
522 line-height: 22px;
523 vertical-align: middle;
523 vertical-align: middle;
524 }
524 }
525
525
526 .icon-add { background-image: url(../images/add.png); }
526 .icon-add { background-image: url(../images/add.png); }
527 .icon-edit { background-image: url(../images/edit.png); }
527 .icon-edit { background-image: url(../images/edit.png); }
528 .icon-copy { background-image: url(../images/copy.png); }
528 .icon-copy { background-image: url(../images/copy.png); }
529 .icon-del { background-image: url(../images/delete.png); }
529 .icon-del { background-image: url(../images/delete.png); }
530 .icon-move { background-image: url(../images/move.png); }
530 .icon-move { background-image: url(../images/move.png); }
531 .icon-save { background-image: url(../images/save.png); }
531 .icon-save { background-image: url(../images/save.png); }
532 .icon-cancel { background-image: url(../images/cancel.png); }
532 .icon-cancel { background-image: url(../images/cancel.png); }
533 .icon-file { background-image: url(../images/file.png); }
533 .icon-file { background-image: url(../images/file.png); }
534 .icon-folder { background-image: url(../images/folder.png); }
534 .icon-folder { background-image: url(../images/folder.png); }
535 .open .icon-folder { background-image: url(../images/folder_open.png); }
535 .open .icon-folder { background-image: url(../images/folder_open.png); }
536 .icon-package { background-image: url(../images/package.png); }
536 .icon-package { background-image: url(../images/package.png); }
537 .icon-home { background-image: url(../images/home.png); }
537 .icon-home { background-image: url(../images/home.png); }
538 .icon-user { background-image: url(../images/user.png); }
538 .icon-user { background-image: url(../images/user.png); }
539 .icon-mypage { background-image: url(../images/user_page.png); }
539 .icon-mypage { background-image: url(../images/user_page.png); }
540 .icon-admin { background-image: url(../images/admin.png); }
540 .icon-admin { background-image: url(../images/admin.png); }
541 .icon-projects { background-image: url(../images/projects.png); }
541 .icon-projects { background-image: url(../images/projects.png); }
542 .icon-logout { background-image: url(../images/logout.png); }
542 .icon-logout { background-image: url(../images/logout.png); }
543 .icon-help { background-image: url(../images/help.png); }
543 .icon-help { background-image: url(../images/help.png); }
544 .icon-attachment { background-image: url(../images/attachment.png); }
544 .icon-attachment { background-image: url(../images/attachment.png); }
545 .icon-index { background-image: url(../images/index.png); }
545 .icon-index { background-image: url(../images/index.png); }
546 .icon-history { background-image: url(../images/history.png); }
546 .icon-history { background-image: url(../images/history.png); }
547 .icon-time { background-image: url(../images/time.png); }
547 .icon-time { background-image: url(../images/time.png); }
548 .icon-stats { background-image: url(../images/stats.png); }
548 .icon-stats { background-image: url(../images/stats.png); }
549 .icon-warning { background-image: url(../images/warning.png); }
549 .icon-warning { background-image: url(../images/warning.png); }
550 .icon-fav { background-image: url(../images/fav.png); }
550 .icon-fav { background-image: url(../images/fav.png); }
551 .icon-fav-off { background-image: url(../images/fav_off.png); }
551 .icon-fav-off { background-image: url(../images/fav_off.png); }
552 .icon-reload { background-image: url(../images/reload.png); }
552 .icon-reload { background-image: url(../images/reload.png); }
553 .icon-lock { background-image: url(../images/locked.png); }
553 .icon-lock { background-image: url(../images/locked.png); }
554 .icon-unlock { background-image: url(../images/unlock.png); }
554 .icon-unlock { background-image: url(../images/unlock.png); }
555 .icon-checked { background-image: url(../images/true.png); }
555 .icon-checked { background-image: url(../images/true.png); }
556 .icon-details { background-image: url(../images/zoom_in.png); }
556 .icon-details { background-image: url(../images/zoom_in.png); }
557 .icon-report { background-image: url(../images/report.png); }
557 .icon-report { background-image: url(../images/report.png); }
558
558
559 .icon22-projects { background-image: url(../images/22x22/projects.png); }
559 .icon22-projects { background-image: url(../images/22x22/projects.png); }
560 .icon22-users { background-image: url(../images/22x22/users.png); }
560 .icon22-users { background-image: url(../images/22x22/users.png); }
561 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
561 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
562 .icon22-role { background-image: url(../images/22x22/role.png); }
562 .icon22-role { background-image: url(../images/22x22/role.png); }
563 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
563 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
564 .icon22-options { background-image: url(../images/22x22/options.png); }
564 .icon22-options { background-image: url(../images/22x22/options.png); }
565 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
565 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
566 .icon22-authent { background-image: url(../images/22x22/authent.png); }
566 .icon22-authent { background-image: url(../images/22x22/authent.png); }
567 .icon22-info { background-image: url(../images/22x22/info.png); }
567 .icon22-info { background-image: url(../images/22x22/info.png); }
568 .icon22-comment { background-image: url(../images/22x22/comment.png); }
568 .icon22-comment { background-image: url(../images/22x22/comment.png); }
569 .icon22-package { background-image: url(../images/22x22/package.png); }
569 .icon22-package { background-image: url(../images/22x22/package.png); }
570 .icon22-settings { background-image: url(../images/22x22/settings.png); }
570 .icon22-settings { background-image: url(../images/22x22/settings.png); }
571 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
571 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
572
572
573 /***** Media print specific styles *****/
573 /***** Media print specific styles *****/
574 @media print {
574 @media print {
575 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
575 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
576 #main { background: #fff; }
576 #main { background: #fff; }
577 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
577 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
578 }
578 }
@@ -1,58 +1,58
1 ---
1 ---
2 time_entries_001:
2 time_entries_001:
3 created_on: 2007-03-23 12:54:18 +01:00
3 created_on: 2007-03-23 12:54:18 +01:00
4 tweek: 12
4 tweek: 12
5 tmonth: 3
5 tmonth: 3
6 project_id: 1
6 project_id: 1
7 comments: My hours
7 comments: My hours
8 updated_on: 2007-03-23 12:54:18 +01:00
8 updated_on: 2007-03-23 12:54:18 +01:00
9 activity_id: 9
9 activity_id: 9
10 spent_on: 2007-03-23
10 spent_on: 2007-03-23
11 issue_id: 1
11 issue_id: 1
12 id: 1
12 id: 1
13 hours: 4.25
13 hours: 4.25
14 user_id: 2
14 user_id: 2
15 tyear: 2007
15 tyear: 2007
16 time_entries_002:
16 time_entries_002:
17 created_on: 2007-03-23 14:11:04 +01:00
17 created_on: 2007-03-23 14:11:04 +01:00
18 tweek: 12
18 tweek: 11
19 tmonth: 3
19 tmonth: 3
20 project_id: 1
20 project_id: 1
21 comments: ""
21 comments: ""
22 updated_on: 2007-03-23 14:11:04 +01:00
22 updated_on: 2007-03-23 14:11:04 +01:00
23 activity_id: 9
23 activity_id: 9
24 spent_on: 2007-03-12
24 spent_on: 2007-03-12
25 issue_id: 1
25 issue_id: 1
26 id: 2
26 id: 2
27 hours: 150.0
27 hours: 150.0
28 user_id: 1
28 user_id: 1
29 tyear: 2007
29 tyear: 2007
30 time_entries_003:
30 time_entries_003:
31 created_on: 2007-04-21 12:20:48 +02:00
31 created_on: 2007-04-21 12:20:48 +02:00
32 tweek: 16
32 tweek: 16
33 tmonth: 4
33 tmonth: 4
34 project_id: 1
34 project_id: 1
35 comments: ""
35 comments: ""
36 updated_on: 2007-04-21 12:20:48 +02:00
36 updated_on: 2007-04-21 12:20:48 +02:00
37 activity_id: 9
37 activity_id: 9
38 spent_on: 2007-04-21
38 spent_on: 2007-04-21
39 issue_id: 2
39 issue_id: 2
40 id: 3
40 id: 3
41 hours: 1.0
41 hours: 1.0
42 user_id: 1
42 user_id: 1
43 tyear: 2007
43 tyear: 2007
44 time_entries_004:
44 time_entries_004:
45 created_on: 2007-04-22 12:20:48 +02:00
45 created_on: 2007-04-22 12:20:48 +02:00
46 tweek: 16
46 tweek: 16
47 tmonth: 4
47 tmonth: 4
48 project_id: 3
48 project_id: 3
49 comments: Time spent on a subproject
49 comments: Time spent on a subproject
50 updated_on: 2007-04-22 12:20:48 +02:00
50 updated_on: 2007-04-22 12:20:48 +02:00
51 activity_id: 10
51 activity_id: 10
52 spent_on: 2007-04-22
52 spent_on: 2007-04-22
53 issue_id:
53 issue_id:
54 id: 4
54 id: 4
55 hours: 7.65
55 hours: 7.65
56 user_id: 1
56 user_id: 1
57 tyear: 2007
57 tyear: 2007
58 No newline at end of file
58
@@ -1,171 +1,180
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'timelog_controller'
19 require 'timelog_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class TimelogController; def rescue_action(e) raise e end; end
22 class TimelogController; def rescue_action(e) raise e end; end
23
23
24 class TimelogControllerTest < Test::Unit::TestCase
24 class TimelogControllerTest < Test::Unit::TestCase
25 fixtures :projects, :roles, :members, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses
25 fixtures :projects, :enabled_modules, :roles, :members, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses
26
26
27 def setup
27 def setup
28 @controller = TimelogController.new
28 @controller = TimelogController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 end
31 end
32
32
33 def test_create
33 def test_create
34 @request.session[:user_id] = 3
34 @request.session[:user_id] = 3
35 post :edit, :project_id => 1,
35 post :edit, :project_id => 1,
36 :time_entry => {:comments => 'Some work on TimelogControllerTest',
36 :time_entry => {:comments => 'Some work on TimelogControllerTest',
37 :activity_id => '10',
37 :activity_id => '10',
38 :spent_on => '2008-03-14',
38 :spent_on => '2008-03-14',
39 :issue_id => '1',
39 :issue_id => '1',
40 :hours => '7.3'}
40 :hours => '7.3'}
41 assert_redirected_to 'projects/ecookbook/timelog/details'
41 assert_redirected_to 'projects/ecookbook/timelog/details'
42
42
43 i = Issue.find(1)
43 i = Issue.find(1)
44 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
44 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
45 assert_not_nil t
45 assert_not_nil t
46 assert_equal 7.3, t.hours
46 assert_equal 7.3, t.hours
47 assert_equal 3, t.user_id
47 assert_equal 3, t.user_id
48 assert_equal i, t.issue
48 assert_equal i, t.issue
49 assert_equal i.project, t.project
49 assert_equal i.project, t.project
50 end
50 end
51
51
52 def test_update
52 def test_update
53 entry = TimeEntry.find(1)
53 entry = TimeEntry.find(1)
54 assert_equal 1, entry.issue_id
54 assert_equal 1, entry.issue_id
55 assert_equal 2, entry.user_id
55 assert_equal 2, entry.user_id
56
56
57 @request.session[:user_id] = 1
57 @request.session[:user_id] = 1
58 post :edit, :id => 1,
58 post :edit, :id => 1,
59 :time_entry => {:issue_id => '2',
59 :time_entry => {:issue_id => '2',
60 :hours => '8'}
60 :hours => '8'}
61 assert_redirected_to 'projects/ecookbook/timelog/details'
61 assert_redirected_to 'projects/ecookbook/timelog/details'
62 entry.reload
62 entry.reload
63
63
64 assert_equal 8, entry.hours
64 assert_equal 8, entry.hours
65 assert_equal 2, entry.issue_id
65 assert_equal 2, entry.issue_id
66 assert_equal 2, entry.user_id
66 assert_equal 2, entry.user_id
67 end
67 end
68
68
69 def destroy
69 def destroy
70 @request.session[:user_id] = 2
70 @request.session[:user_id] = 2
71 post :destroy, :id => 1
71 post :destroy, :id => 1
72 assert_redirected_to 'projects/ecookbook/timelog/details'
72 assert_redirected_to 'projects/ecookbook/timelog/details'
73 assert_nil TimeEntry.find_by_id(1)
73 assert_nil TimeEntry.find_by_id(1)
74 end
74 end
75
75
76 def test_report_no_criteria
76 def test_report_no_criteria
77 get :report, :project_id => 1
77 get :report, :project_id => 1
78 assert_response :success
78 assert_response :success
79 assert_template 'report'
79 assert_template 'report'
80 end
80 end
81
81
82 def test_report_all_time
82 def test_report_all_time
83 get :report, :project_id => 1, :criterias => ['project', 'issue']
83 get :report, :project_id => 1, :criterias => ['project', 'issue']
84 assert_response :success
84 assert_response :success
85 assert_template 'report'
85 assert_template 'report'
86 assert_not_nil assigns(:total_hours)
86 assert_not_nil assigns(:total_hours)
87 assert_equal "162.90", "%.2f" % assigns(:total_hours)
87 assert_equal "162.90", "%.2f" % assigns(:total_hours)
88 end
88 end
89
89
90 def test_report_all_time_by_day
91 get :report, :project_id => 1, :criterias => ['project', 'issue'], :columns => 'day'
92 assert_response :success
93 assert_template 'report'
94 assert_not_nil assigns(:total_hours)
95 assert_equal "162.90", "%.2f" % assigns(:total_hours)
96 assert_tag :tag => 'th', :content => '2007-03-12'
97 end
98
90 def test_report_one_criteria
99 def test_report_one_criteria
91 get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project']
100 get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project']
92 assert_response :success
101 assert_response :success
93 assert_template 'report'
102 assert_template 'report'
94 assert_not_nil assigns(:total_hours)
103 assert_not_nil assigns(:total_hours)
95 assert_equal "8.65", "%.2f" % assigns(:total_hours)
104 assert_equal "8.65", "%.2f" % assigns(:total_hours)
96 end
105 end
97
106
98 def test_report_two_criterias
107 def test_report_two_criterias
99 get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"]
108 get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"]
100 assert_response :success
109 assert_response :success
101 assert_template 'report'
110 assert_template 'report'
102 assert_not_nil assigns(:total_hours)
111 assert_not_nil assigns(:total_hours)
103 assert_equal "162.90", "%.2f" % assigns(:total_hours)
112 assert_equal "162.90", "%.2f" % assigns(:total_hours)
104 end
113 end
105
114
106 def test_report_one_criteria_no_result
115 def test_report_one_criteria_no_result
107 get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project']
116 get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project']
108 assert_response :success
117 assert_response :success
109 assert_template 'report'
118 assert_template 'report'
110 assert_not_nil assigns(:total_hours)
119 assert_not_nil assigns(:total_hours)
111 assert_equal "0.00", "%.2f" % assigns(:total_hours)
120 assert_equal "0.00", "%.2f" % assigns(:total_hours)
112 end
121 end
113
122
114 def test_details_at_project_level
123 def test_details_at_project_level
115 get :details, :project_id => 1
124 get :details, :project_id => 1
116 assert_response :success
125 assert_response :success
117 assert_template 'details'
126 assert_template 'details'
118 assert_not_nil assigns(:entries)
127 assert_not_nil assigns(:entries)
119 assert_equal 4, assigns(:entries).size
128 assert_equal 4, assigns(:entries).size
120 # project and subproject
129 # project and subproject
121 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
130 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
122 assert_not_nil assigns(:total_hours)
131 assert_not_nil assigns(:total_hours)
123 assert_equal "162.90", "%.2f" % assigns(:total_hours)
132 assert_equal "162.90", "%.2f" % assigns(:total_hours)
124 # display all time by default
133 # display all time by default
125 assert_nil assigns(:from)
134 assert_equal '2007-03-11'.to_date, assigns(:from)
126 assert_nil assigns(:to)
135 assert_equal '2007-04-22'.to_date, assigns(:to)
127 end
136 end
128
137
129 def test_details_at_project_level_with_date_range
138 def test_details_at_project_level_with_date_range
130 get :details, :project_id => 1, :from => '2007-03-20', :to => '2007-04-30'
139 get :details, :project_id => 1, :from => '2007-03-20', :to => '2007-04-30'
131 assert_response :success
140 assert_response :success
132 assert_template 'details'
141 assert_template 'details'
133 assert_not_nil assigns(:entries)
142 assert_not_nil assigns(:entries)
134 assert_equal 3, assigns(:entries).size
143 assert_equal 3, assigns(:entries).size
135 assert_not_nil assigns(:total_hours)
144 assert_not_nil assigns(:total_hours)
136 assert_equal "12.90", "%.2f" % assigns(:total_hours)
145 assert_equal "12.90", "%.2f" % assigns(:total_hours)
137 assert_equal '2007-03-20'.to_date, assigns(:from)
146 assert_equal '2007-03-20'.to_date, assigns(:from)
138 assert_equal '2007-04-30'.to_date, assigns(:to)
147 assert_equal '2007-04-30'.to_date, assigns(:to)
139 end
148 end
140
149
141 def test_details_at_project_level_with_period
150 def test_details_at_project_level_with_period
142 get :details, :project_id => 1, :period => '7_days'
151 get :details, :project_id => 1, :period => '7_days'
143 assert_response :success
152 assert_response :success
144 assert_template 'details'
153 assert_template 'details'
145 assert_not_nil assigns(:entries)
154 assert_not_nil assigns(:entries)
146 assert_not_nil assigns(:total_hours)
155 assert_not_nil assigns(:total_hours)
147 assert_equal Date.today - 7, assigns(:from)
156 assert_equal Date.today - 7, assigns(:from)
148 assert_equal Date.today, assigns(:to)
157 assert_equal Date.today, assigns(:to)
149 end
158 end
150
159
151 def test_details_at_issue_level
160 def test_details_at_issue_level
152 get :details, :issue_id => 1
161 get :details, :issue_id => 1
153 assert_response :success
162 assert_response :success
154 assert_template 'details'
163 assert_template 'details'
155 assert_not_nil assigns(:entries)
164 assert_not_nil assigns(:entries)
156 assert_equal 2, assigns(:entries).size
165 assert_equal 2, assigns(:entries).size
157 assert_not_nil assigns(:total_hours)
166 assert_not_nil assigns(:total_hours)
158 assert_equal 154.25, assigns(:total_hours)
167 assert_equal 154.25, assigns(:total_hours)
159 # display all time by default
168 # display all time by default
160 assert_nil assigns(:from)
169 assert_equal '2007-03-11'.to_date, assigns(:from)
161 assert_nil assigns(:to)
170 assert_equal '2007-04-22'.to_date, assigns(:to)
162 end
171 end
163
172
164 def test_details_csv_export
173 def test_details_csv_export
165 get :details, :project_id => 1, :format => 'csv'
174 get :details, :project_id => 1, :format => 'csv'
166 assert_response :success
175 assert_response :success
167 assert_equal 'text/csv', @response.content_type
176 assert_equal 'text/csv', @response.content_type
168 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment\n")
177 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment\n")
169 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,2,Feature request,Add ingredients categories,1.0,\"\"\n")
178 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,2,Feature request,Add ingredients categories,1.0,\"\"\n")
170 end
179 end
171 end
180 end
General Comments 0
You need to be logged in to leave comments. Login now