timesheet_plugin 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. data/COPYRIGHT.txt +16 -0
  2. data/CREDITS.txt +21 -0
  3. data/GPL.txt +339 -0
  4. data/README.rdoc +56 -0
  5. data/Rakefile +33 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/timesheet_controller.rb +145 -0
  8. data/app/helpers/timesheet_helper.rb +42 -0
  9. data/app/models/timesheet.rb +277 -0
  10. data/app/views/settings/_timesheet_settings.rhtml +4 -0
  11. data/app/views/timesheet/_by_issue.rhtml +35 -0
  12. data/app/views/timesheet/_form.rhtml +90 -0
  13. data/app/views/timesheet/_issue_time_entries.rhtml +43 -0
  14. data/app/views/timesheet/_time_entry.rhtml +31 -0
  15. data/app/views/timesheet/_timesheet_group.rhtml +21 -0
  16. data/app/views/timesheet/context_menu.html.erb +4 -0
  17. data/app/views/timesheet/index.rhtml +11 -0
  18. data/app/views/timesheet/no_projects.rhtml +5 -0
  19. data/app/views/timesheet/report.rhtml +52 -0
  20. data/app/views/timesheet/timelog.rhtml +5 -0
  21. data/assets/images/toggle-arrow-closed.gif +0 -0
  22. data/assets/images/toggle-arrow-open.gif +0 -0
  23. data/config/locales/ca.yml +9 -0
  24. data/config/locales/cs.yml +7 -0
  25. data/config/locales/da.yml +10 -0
  26. data/config/locales/de.yml +10 -0
  27. data/config/locales/en.yml +10 -0
  28. data/config/locales/es.yml +9 -0
  29. data/config/locales/fr.yml +10 -0
  30. data/config/locales/hu.yml +10 -0
  31. data/config/locales/it.yml +7 -0
  32. data/config/locales/lt.yml +9 -0
  33. data/config/locales/pl.yml +10 -0
  34. data/config/locales/ru.yml +9 -0
  35. data/init.rb +2 -0
  36. data/lang/ca.yml +8 -0
  37. data/lang/cs.yml +6 -0
  38. data/lang/da.yml +9 -0
  39. data/lang/de.yml +9 -0
  40. data/lang/en.yml +9 -0
  41. data/lang/es.yml +8 -0
  42. data/lang/fr.yml +9 -0
  43. data/lang/hu.yml +9 -0
  44. data/lang/it.yml +6 -0
  45. data/lang/lt.yml +8 -0
  46. data/lang/pl.yml +9 -0
  47. data/lang/ru.yml +8 -0
  48. data/lib/tasks/plugin_stat.rake +38 -0
  49. data/lib/timesheet_compatibility.rb +14 -0
  50. data/rails/init.rb +29 -0
  51. data/spec/controllers/timesheet_controller_spec.rb +263 -0
  52. data/spec/models/timesheet_spec.rb +537 -0
  53. data/spec/sanity_spec.rb +7 -0
  54. data/spec/spec_helper.rb +40 -0
  55. metadata +107 -0
@@ -0,0 +1,277 @@
1
+ class Timesheet
2
+ attr_accessor :date_from, :date_to, :projects, :activities, :users, :allowed_projects, :period, :period_type
3
+
4
+ # Time entries on the Timesheet in the form of:
5
+ # project.name => {:logs => [time entries], :users => [users shown in logs] }
6
+ # project.name => {:logs => [time entries], :users => [users shown in logs] }
7
+ # project.name could be the parent project name also
8
+ attr_accessor :time_entries
9
+
10
+ # Array of TimeEntry ids to fetch
11
+ attr_accessor :potential_time_entry_ids
12
+
13
+ # Sort time entries by this field
14
+ attr_accessor :sort
15
+ ValidSortOptions = {
16
+ :project => 'Project',
17
+ :user => 'User',
18
+ :issue => 'Issue'
19
+ }
20
+
21
+ ValidPeriodType = {
22
+ :free_period => 0,
23
+ :default => 1
24
+ }
25
+
26
+ def initialize(options = { })
27
+ self.projects = [ ]
28
+ self.time_entries = options[:time_entries] || { }
29
+ self.potential_time_entry_ids = options[:potential_time_entry_ids] || [ ]
30
+ self.allowed_projects = options[:allowed_projects] || [ ]
31
+
32
+ unless options[:activities].nil?
33
+ self.activities = options[:activities].collect { |a| a.to_i }
34
+ else
35
+ self.activities = TimesheetCompatibility::Enumeration::activities.collect { |a| a.id.to_i }
36
+ end
37
+
38
+ unless options[:users].nil?
39
+ self.users = options[:users].collect { |u| u.to_i }
40
+ else
41
+ self.users = User.find(:all).collect(&:id)
42
+ end
43
+
44
+ if !options[:sort].nil? && options[:sort].respond_to?(:to_sym) && ValidSortOptions.keys.include?(options[:sort].to_sym)
45
+ self.sort = options[:sort].to_sym
46
+ else
47
+ self.sort = :project
48
+ end
49
+
50
+ self.date_from = options[:date_from] || Date.today.to_s
51
+ self.date_to = options[:date_to] || Date.today.to_s
52
+
53
+ if options[:period_type] && ValidPeriodType.values.include?(options[:period_type].to_i)
54
+ self.period_type = options[:period_type].to_i
55
+ else
56
+ self.period_type = ValidPeriodType[:free_period]
57
+ end
58
+ self.period = options[:period] || nil
59
+ end
60
+
61
+ # Gets all the time_entries for all the projects
62
+ def fetch_time_entries
63
+ self.time_entries = { }
64
+ case self.sort
65
+ when :project
66
+ fetch_time_entries_by_project
67
+ when :user
68
+ fetch_time_entries_by_user
69
+ when :issue
70
+ fetch_time_entries_by_issue
71
+ else
72
+ fetch_time_entries_by_project
73
+ end
74
+ end
75
+
76
+ def period=(period)
77
+ return if self.period_type == Timesheet::ValidPeriodType[:free_period]
78
+ # Stolen from the TimelogController
79
+ case period.to_s
80
+ when 'today'
81
+ self.date_from = self.date_to = Date.today
82
+ when 'yesterday'
83
+ self.date_from = self.date_to = Date.today - 1
84
+ when 'current_week' # Mon -> Sun
85
+ self.date_from = Date.today - (Date.today.cwday - 1)%7
86
+ self.date_to = self.date_from + 6
87
+ when 'last_week'
88
+ self.date_from = Date.today - 7 - (Date.today.cwday - 1)%7
89
+ self.date_to = self.date_from + 6
90
+ when '7_days'
91
+ self.date_from = Date.today - 7
92
+ self.date_to = Date.today
93
+ when 'current_month'
94
+ self.date_from = Date.civil(Date.today.year, Date.today.month, 1)
95
+ self.date_to = (self.date_from >> 1) - 1
96
+ when 'last_month'
97
+ self.date_from = Date.civil(Date.today.year, Date.today.month, 1) << 1
98
+ self.date_to = (self.date_from >> 1) - 1
99
+ when '30_days'
100
+ self.date_from = Date.today - 30
101
+ self.date_to = Date.today
102
+ when 'current_year'
103
+ self.date_from = Date.civil(Date.today.year, 1, 1)
104
+ self.date_to = Date.civil(Date.today.year, 12, 31)
105
+ when 'all'
106
+ self.date_from = self.date_to = nil
107
+ end
108
+ self
109
+ end
110
+
111
+ protected
112
+
113
+ def conditions(users)
114
+ if self.potential_time_entry_ids.empty?
115
+ if self.date_from && self.date_to
116
+ conditions = ["spent_on >= (?) AND spent_on <= (?) AND #{TimeEntry.table_name}.project_id IN (?) AND activity_id IN (?) AND user_id IN (?)",
117
+ self.date_from, self.date_to, self.projects, self.activities, users ]
118
+ else # All time
119
+ conditions = ["#{TimeEntry.table_name}.project_id IN (?) AND activity_id IN (?) AND user_id IN (?)",
120
+ self.projects, self.activities, users ]
121
+ end
122
+ else
123
+ conditions = ["user_id IN (?) AND #{TimeEntry.table_name}.id IN (?)",
124
+ users, self.potential_time_entry_ids ]
125
+ end
126
+
127
+ Redmine::Hook.call_hook(:plugin_timesheet_model_timesheet_conditions, { :timesheet => self, :conditions => conditions})
128
+ return conditions
129
+ end
130
+
131
+ def includes
132
+ includes = [:activity, :user, :project, {:issue => [:tracker, :assigned_to, :priority]}]
133
+ Redmine::Hook.call_hook(:plugin_timesheet_model_timesheet_includes, { :timesheet => self, :includes => includes})
134
+ return includes
135
+ end
136
+
137
+ private
138
+
139
+
140
+ def time_entries_for_all_users(project)
141
+ return project.time_entries.find(:all,
142
+ :conditions => self.conditions(self.users),
143
+ :include => self.includes,
144
+ :order => "spent_on ASC")
145
+ end
146
+
147
+ def time_entries_for_current_user(project)
148
+ return project.time_entries.find(:all,
149
+ :conditions => self.conditions(User.current.id),
150
+ :include => self.includes,
151
+ :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
152
+ :order => "spent_on ASC")
153
+ end
154
+
155
+ def issue_time_entries_for_all_users(issue)
156
+ return issue.time_entries.find(:all,
157
+ :conditions => self.conditions(self.users),
158
+ :include => self.includes,
159
+ :include => [:activity, :user],
160
+ :order => "spent_on ASC")
161
+ end
162
+
163
+ def issue_time_entries_for_current_user(issue)
164
+ return issue.time_entries.find(:all,
165
+ :conditions => self.conditions(User.current.id),
166
+ :include => self.includes,
167
+ :include => [:activity, :user],
168
+ :order => "spent_on ASC")
169
+ end
170
+
171
+ def time_entries_for_user(user)
172
+ return TimeEntry.find(:all,
173
+ :conditions => self.conditions([user]),
174
+ :include => self.includes,
175
+ :order => "spent_on ASC"
176
+ )
177
+ end
178
+
179
+ def fetch_time_entries_by_project
180
+ self.projects.each do |project|
181
+ logs = []
182
+ users = []
183
+ if User.current.admin?
184
+ # Administrators can see all time entries
185
+ logs = time_entries_for_all_users(project)
186
+ users = logs.collect(&:user).uniq.sort
187
+ elsif User.current.allowed_to?(:see_project_timesheets, project)
188
+ # Users with the Role and correct permission can see all time entries
189
+ logs = time_entries_for_all_users(project)
190
+ users = logs.collect(&:user).uniq.sort
191
+ elsif User.current.allowed_to?(:view_time_entries, project)
192
+ # Users with permission to see their time entries
193
+ logs = time_entries_for_current_user(project)
194
+ users = logs.collect(&:user).uniq.sort
195
+ else
196
+ # Rest can see nothing
197
+ end
198
+
199
+ # Append the parent project name
200
+ if project.parent.nil?
201
+ unless logs.empty?
202
+ self.time_entries[project.name] = { :logs => logs, :users => users }
203
+ end
204
+ else
205
+ unless logs.empty?
206
+ self.time_entries[project.parent.name + ' / ' + project.name] = { :logs => logs, :users => users }
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ def fetch_time_entries_by_user
213
+ self.users.each do |user_id|
214
+ logs = []
215
+ if User.current.admin?
216
+ # Administrators can see all time entries
217
+ logs = time_entries_for_user(user_id)
218
+ elsif User.current.id == user_id
219
+ # Users can see their own their time entries
220
+ logs = time_entries_for_user(user_id)
221
+ else
222
+ # Rest can see nothing
223
+ end
224
+
225
+ unless logs.empty?
226
+ user = User.find_by_id(user_id)
227
+ self.time_entries[user.name] = { :logs => logs } unless user.nil?
228
+ end
229
+ end
230
+ end
231
+
232
+ # project => { :users => [users shown in logs],
233
+ # :issues =>
234
+ # { issue => {:logs => [time entries],
235
+ # issue => {:logs => [time entries],
236
+ # issue => {:logs => [time entries]}
237
+ #
238
+ def fetch_time_entries_by_issue
239
+ self.projects.each do |project|
240
+ logs = []
241
+ users = []
242
+ project.issues.each do |issue|
243
+ if User.current.admin?
244
+ # Administrators can see all time entries
245
+ logs << issue_time_entries_for_all_users(issue)
246
+ elsif User.current.allowed_to?(:see_project_timesheets, project)
247
+ # Users with the Role and correct permission can see all time entries
248
+ logs << issue_time_entries_for_all_users(issue)
249
+ elsif User.current.allowed_to?(:view_time_entries, project)
250
+ # Users with permission to see their time entries
251
+ logs << issue_time_entries_for_current_user(issue)
252
+ else
253
+ # Rest can see nothing
254
+ end
255
+ end
256
+
257
+ logs.flatten! if logs.respond_to?(:flatten!)
258
+ logs.uniq! if logs.respond_to?(:uniq!)
259
+
260
+ unless logs.empty?
261
+ users << logs.collect(&:user).uniq.sort
262
+
263
+
264
+ issues = logs.collect(&:issue).uniq
265
+ issue_logs = { }
266
+ issues.each do |issue|
267
+ issue_logs[issue] = logs.find_all {|time_log| time_log.issue == issue } # TimeEntry is for this issue
268
+ end
269
+
270
+ # TODO: TE without an issue
271
+
272
+ self.time_entries[project] = { :issues => issue_logs, :users => users}
273
+ end
274
+ end
275
+ end
276
+
277
+ end
@@ -0,0 +1,4 @@
1
+ <p><label>List size</label><%= text_field_tag 'settings[list_size]', @settings['list_size'] %></p>
2
+
3
+ <p><label>Number precision</label><%= text_field_tag 'settings[precision]', @settings['precision'] %></p>
4
+
@@ -0,0 +1,35 @@
1
+ <table class="list issues">
2
+ <thead>
3
+ <th width="2%">
4
+ <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "table")); return false;',
5
+ :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
6
+ </th>
7
+ <th width="2%">&nbsp;</th>
8
+ <th width="8%"><%= l(:label_date) %></th>
9
+ <th width="10%"><%= l(:label_member) %></th>
10
+ <th width="45%"><%= l(:label_issue) %> / <%= l(:field_comments) %></th>
11
+ <th width="10%"><%= l(:field_hours) %>
12
+ <%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_group_header, { }) %>
13
+ <th align="center"></td>
14
+ </thead>
15
+ <tbody>
16
+ <%= render :partial => "issue_time_entries", :collection => entry[:issues] %>
17
+ </tbody>
18
+ </table>
19
+ <br />
20
+ <script type="text/javascript">
21
+ function toggleTimeEntries(id) {
22
+ $$('.issue-time-entry-' + id).each(function(ele) { ele.toggle();} )
23
+ $$('.toggle-' + id).each(function(ele) { ele.toggle();} )
24
+ }
25
+
26
+ /*
27
+ * Checks all the Time Entries under issue_id
28
+ */
29
+ function toggleTimeEntriesSelection(issue_id) {
30
+ $$('.issue-time-entry-' + issue_id).each(function(ele) {
31
+ toggleIssuesSelection(ele);
32
+ });
33
+ }
34
+
35
+ </script>
@@ -0,0 +1,90 @@
1
+ <div id="timesheet-form">
2
+ <fieldset>
3
+ <% form_for :timesheet, :url =>{:action => 'report'} do |f| %>
4
+
5
+ <p id="date-options">
6
+ <label><%= l(:label_date)%>:</label><br />
7
+ <%= radio_button_tag 'timesheet[period_type]', '1', @timesheet.period_type == Timesheet::ValidPeriodType[:default] %>
8
+ <%= select_tag 'timesheet[period]', options_for_period_select((params[:timesheet].nil? ? nil : params[:timesheet][:period])),
9
+ :onfocus => '$("timesheet_period_type_1").checked = true;' %>
10
+ <br /><br />
11
+
12
+ <%= radio_button_tag 'timesheet[period_type]', '2', @timesheet.period_type == Timesheet::ValidPeriodType[:free_period] %>
13
+ <span onclick="$('timesheet_period_type_2').checked = true;">
14
+ <label for="timesheet_date_from"><%= l(:timesheet_date_from_label)%>:</label><br />
15
+ <%= f.text_field "date_from", :size => 10 %><%= calendar_for('timesheet_date_from') %><br />
16
+
17
+ <label for="timesheet_date_to"><%= l(:timesheet_date_to_label)%>:</label><br />
18
+ <%= f.text_field "date_to", :size => 10 %><%= calendar_for('timesheet_date_to') %><br /><br />
19
+ </span>
20
+ </p>
21
+
22
+ <p>
23
+ <label for="timesheet_sort"><%= l(:timesheet_group_by) %>:</label><br />
24
+ <%= select_tag("timesheet[sort]", options_for_select(Timesheet::ValidSortOptions.invert, @timesheet.sort)) %>
25
+
26
+ </p>
27
+
28
+ <p>
29
+ <label for="timesheet[projects][]" class="select_all"><%= l(:timesheet_project_label)%>:</label><br />
30
+ <%= select_tag 'timesheet[projects][]',
31
+ options_from_collection_for_select(@timesheet.allowed_projects, :id, :name, @timesheet.projects.collect(&:id)),
32
+ { :multiple => true, :size => @list_size}
33
+ %>
34
+ </p>
35
+
36
+
37
+ <p>
38
+ <label for="timesheet[activities][]" class="select_all"><%= l(:timesheet_activities_label)%>:</label><br />
39
+ <%= select_tag 'timesheet[activities][]',
40
+ options_from_collection_for_select(@activities, :id, :name, @timesheet.activities),
41
+ { :multiple => true, :size => @list_size}
42
+ %>
43
+ </p>
44
+
45
+ <p>
46
+ <label for="timesheet[users][]" class="select_all"><%= l(:timesheet_users_label)%>:</label><br />
47
+ <%= select_tag 'timesheet[users][]',
48
+ options_from_collection_for_select(User.find(:all, :conditions => ['status = ?', User::STATUS_ACTIVE]).sort { |a,b| a.to_s.downcase <=> b.to_s.downcase }, :id, :name, @timesheet.users),
49
+ { :multiple => true, :size => @list_size}
50
+
51
+ %>
52
+ </p>
53
+
54
+ <%# TODO: Typo on hook %>
55
+ <%= call_hook(:plugin_timesheet_view_timesheet_form, { :timesheet => @timesheet, :params => params, :list_size => @list_size }) %>
56
+ <%= call_hook(:plugin_timesheet_views_timesheet_form, { :timesheet => @timesheet, :params => params, :list_size => @list_size }) %>
57
+
58
+ <div class="clear"></div>
59
+ <%= submit_tag l(:button_apply),:class => 'button-small' -%>
60
+
61
+ <% end %>
62
+ </fieldset>
63
+ </div>
64
+
65
+ <% content_for(:header_tags) do %>
66
+ <style type="text/css">
67
+ #date-options { margin-left: 10px; }
68
+ #date-options input[type='radio'] { margin-left: -20px; }
69
+ </style>
70
+ <script type="text/javascript">
71
+
72
+ function targetField(label_element) {
73
+ return $(label_element.attributes.for.value);
74
+ }
75
+
76
+ function selectAllOptions(element) {
77
+ for (var i = 0; i < element.options.length; i++) {
78
+ element.options[i].selected = true;
79
+ }
80
+ }
81
+
82
+ Event.observe(window, 'load',
83
+ function() {
84
+ $$('label.select_all').each(function(element) {
85
+ Event.observe(element, 'click', function (e) { selectAllOptions(targetField(this)); });
86
+ });
87
+ }
88
+ );
89
+ </script>
90
+ <% end %>
@@ -0,0 +1,43 @@
1
+ <% issue = issue_time_entries[0] %>
2
+ <% time_entries = issue_time_entries[1] %>
3
+ <% unless issue.nil? %>
4
+ <tr class="<%= cycle("odd", "even") %>">
5
+ <td align="center">
6
+ <%= link_to image_tag('toggle_check.png'), {}, :onclick => "toggleTimeEntriesSelection('#{issue.id}'); return false;",
7
+ :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
8
+ </td>
9
+ <td align="center">
10
+ <%= toggle_issue_arrows(issue.id) %>
11
+ </td>
12
+ <td align="center"></td>
13
+ <td align="center"><%= issue.assigned_to.to_s %></td>
14
+ <td><%= link_to("##{issue.id}", :controller => 'issues', :action => 'show', :id => issue.id) %>: <%= h issue.subject %></td>
15
+ <td align="center"><strong><%= number_with_precision(displayed_time_entries_for_issue(time_entries), @precision) %></strong></td>
16
+ <%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry_sum, {:time_entries => time_entries, :precision => @precision }) %>
17
+ <td align="center"></td>
18
+ </tr>
19
+ <% time_entries.each do |time_entry| %>
20
+ <%# TODO: Typo on hook %>
21
+ <tr class="<%= cycle("odd", "even") %> issue-time-entry-<%= issue.id -%> hascontextmenu <%= call_hook(:plugin_timesheet_view_timesheets_time_entry_row_class, {:time_entry => time_entry }) %> <%= call_hook(:plugin_timesheet_views_timesheets_time_entry_row_class, {:time_entry => time_entry }) %>" style="display:none;">
22
+ <td align="center">
23
+ <%= check_box_tag 'ids[]', time_entry.id, false, { :class => 'checkbox' } %>
24
+ </td>
25
+ <td align="center"></td>
26
+ <td align="center"><%= format_date(time_entry.spent_on) %></td>
27
+ <td align="center"><%= time_entry.user.name %></td>
28
+ <td><%= h time_entry.comments %></td>
29
+ <td align="center"><strong><%= number_with_precision(time_entry.hours, @precision) %></strong></td>
30
+ <%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry, {:time_entry => time_entry, :precision => @precision }) %>
31
+ <td align="center">
32
+ <% if time_entry.editable_by?(User.current) -%>
33
+ <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => time_entry},
34
+ :title => l(:button_edit) %>
35
+ <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => time_entry},
36
+ :confirm => l(:text_are_you_sure),
37
+ :method => :post,
38
+ :title => l(:button_delete) %>
39
+ <% end -%>
40
+ </td>
41
+ </tr>
42
+ <% end %>
43
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <%# TODO: Typo on hook %>
2
+ <tr id="time_entry_<%= time_entry.id %>" class="time_entry <%= cycle("odd", "even") %> hascontextmenu <%= call_hook(:plugin_timesheet_view_timesheets_time_entry_row_class, {:time_entry => time_entry }) %> <%= call_hook(:plugin_timesheet_views_timesheets_time_entry_row_class, {:time_entry => time_entry }) %>">
3
+ <td align="center"><%= check_box_tag 'ids[]', time_entry.id, false, { :class => 'checkbox' } %></td>
4
+ <td align="center"><%= format_date(time_entry.spent_on) %></td>
5
+ <td align="center"><%= time_entry.user.name %></td>
6
+ <td align="center"><%= time_entry.activity.name %></td>
7
+ <td align="center"><%= time_entry.project.name %></td>
8
+ <td align="center">
9
+ <% if time_entry.issue %>
10
+ <div class="tooltip">
11
+ <%= link_to_issue time_entry.issue %>
12
+ <span class="tip">
13
+ <%= render_issue_tooltip time_entry.issue %>
14
+ </span>
15
+ </div>
16
+ <% end %>
17
+ </td>
18
+ <td><%=h time_entry.comments %></td>
19
+ <td align="center"><strong><%= number_with_precision(time_entry.hours, @precision) %></strong></td>
20
+ <%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry, {:time_entry => time_entry, :precision => @precision }) %>
21
+ <td align="center">
22
+ <% if time_entry.editable_by?(User.current) -%>
23
+ <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => time_entry},
24
+ :title => l(:button_edit) %>
25
+ <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => time_entry},
26
+ :confirm => l(:text_are_you_sure),
27
+ :method => :post,
28
+ :title => l(:button_delete) %>
29
+ <% end -%>
30
+ </td>
31
+ </tr>
@@ -0,0 +1,21 @@
1
+ <table class="list issues">
2
+ <thead>
3
+ <th width="2%">
4
+ <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "table")); return false;',
5
+ :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
6
+ </th>
7
+ <th width="8%"><%= l(:label_date) %></th>
8
+ <th width="10%"><%= l(:label_member) %></th>
9
+ <th width="15%"><%= l(:label_activity) %></th>
10
+ <th width="15%"><%= l(:label_project) %></th>
11
+ <th width="10%"><%= l(:label_issue) %></th>
12
+ <th width="25%"><%= l(:field_comments) %></th>
13
+ <th width="10%"><%= l(:field_hours) %>
14
+ <%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_group_header, { }) %>
15
+ <th></th>
16
+ </thead>
17
+ <tbody>
18
+ <%= render :partial => "time_entry", :collection => entry[:logs] %>
19
+ </tbody>
20
+ </table>
21
+ <br />
@@ -0,0 +1,4 @@
1
+ <%# TODO: Typo on hook %>
2
+ <% entries = call_hook(:plugin_timesheet_view_timesheets_context_menu, { :time_entries => @time_entries }) %>
3
+ <% entries += call_hook(:plugin_timesheet_views_timesheets_context_menu, { :time_entries => @time_entries }) %>
4
+ <%= content_tag(:ul, entries) unless entries.empty? %>
@@ -0,0 +1,11 @@
1
+ <h2><%= l(:timesheet_title)%></h2>
2
+
3
+ <%= render :partial => 'form' %>
4
+
5
+ <% content_for(:header_tags) do %>
6
+ <style type="text/css">
7
+
8
+ div#timesheet-form p { padding:0px 10px; float:left; }
9
+
10
+ </style>
11
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <h2><%= l(:timesheet_title)%></h2>
2
+
3
+ <p class="nodata">
4
+ <%= l(:label_no_data) %>
5
+ </p>
@@ -0,0 +1,52 @@
1
+ <div class="contextual">
2
+ <%= permalink_to_timesheet(@timesheet) %>
3
+ </div>
4
+
5
+ <h2><%= l(:timesheet_title)%></h2>
6
+
7
+ <%= render :partial => 'form' %>
8
+
9
+ <%= call_hook(:plugin_timesheet_views_timesheets_report_before_time_entries, { :timesheet => @timesheet }) %>
10
+
11
+ <% form_tag({}, { :id => 'time_entries'}) do -%>
12
+ <% if @timesheet.time_entries.length > 0 %>
13
+ <h2><%= l(:label_spent_time) %> (<%= h(number_with_precision(@grand_total, @precision)) -%> <%= h(l(:field_hours)) -%>)</h2>
14
+
15
+ <% @timesheet.time_entries.each do |entryname,entry|
16
+ case @timesheet.sort
17
+ when :user %>
18
+ <h3><%= h entryname -%> (<%= h number_with_precision(@total[entryname], @precision) %> <%= h(l(:field_hours)) -%>)</h3>
19
+ <%= render :partial => 'timesheet_group', :locals => {:entry => entry, :name => entryname, :total => @total[entryname] } %>
20
+ <% when :issue %>
21
+ <h3><%= h entryname -%> (<%= h number_with_precision(@total[entryname], @precision) %> <%= h(l(:field_hours)) -%>)</h3>
22
+ <%= render :partial => 'by_issue', :locals => {:entry => entry, :name => entryname, :total => 0 } %>
23
+ <% else %>
24
+ <%# Default to :project %>
25
+ <h3><%= h entryname -%> (<%= h number_with_precision(@total[entryname], @precision) %> <%= h(l(:field_hours)) -%>) <%= showing_users(entry[:users]) %></h3>
26
+ <%= render :partial => 'timesheet_group', :locals => {:entry => entry, :name => entryname, :total => @total[entryname] } %>
27
+
28
+ <% end
29
+ end # each
30
+ end # length
31
+ end # form_tag
32
+ -%>
33
+
34
+ <% content_for(:header_tags) do %>
35
+ <%= javascript_include_tag 'context_menu' %>
36
+ <%= stylesheet_link_tag 'context_menu' %>
37
+ <style type="text/css">
38
+
39
+ div#timesheet-form p { padding:0px 10px; float:left; }
40
+
41
+ </style>
42
+ <%# TODO: Typo on hook %>
43
+ <%= call_hook(:plugin_timesheet_view_timesheets_report_header_tags, { :timesheet => @timesheet }) %>
44
+ <%= call_hook(:plugin_timesheet_views_timesheets_report_header_tags, { :timesheet => @timesheet }) %>
45
+ <% end %>
46
+
47
+ <div id="context-menu" style="display: none;"></div>
48
+ <%= javascript_tag "new ContextMenu('#{url_for(:controller => 'timesheet', :action => 'context_menu')}')" %>
49
+
50
+ <%# TODO: Typo on hook %>
51
+ <%= call_hook(:plugin_timesheet_view_timesheets_report_bottom, { :timesheet => @timesheet }) %>
52
+ <%= call_hook(:plugin_timesheet_views_timesheets_report_bottom, { :timesheet => @timesheet }) %>
@@ -0,0 +1,5 @@
1
+ <h2><%= l(:label_spent_time) %></h2>
2
+
3
+ <% @entries.each do |entryname,entry| -%>
4
+ <%= render :partial => 'project_timesheet', :locals => {:entry => entry, :name => entryname} %>
5
+ <% end -%>
Binary file
@@ -0,0 +1,9 @@
1
+ ca:
2
+ timesheet_title: Full de temps
3
+ timesheet_date_from_label: De
4
+ timesheet_date_to_label: A
5
+ timesheet_project_label: Projecte
6
+ timesheet_activities_label: Activitat
7
+ timesheet_users_label: Usuaris
8
+ timesheet_showing_users: 'mostrant temps per a: '
9
+ timesheet_permalink: "(Enlla&ccedil; permanent a aquest full de temps)"
@@ -0,0 +1,7 @@
1
+ cs:
2
+ timesheet_title: Souhrnná tabulka stráveného času
3
+ timesheet_date_from_label: Od
4
+ timesheet_date_to_label: do
5
+ timesheet_project_label: Projekt
6
+ timesheet_activities_label: Aktivita
7
+ timesheet_users_label: Uživatelé
@@ -0,0 +1,10 @@
1
+ da:
2
+ timesheet_title: Timeseddel
3
+ timesheet_date_from_label: Fra
4
+ timesheet_date_to_label: Til
5
+ timesheet_project_label: Projekt
6
+ timesheet_activities_label: Aktivitet
7
+ timesheet_users_label: Brugere
8
+ timesheet_showing_users: 'viser tidsregistreringer for: '
9
+ timesheet_permalink: '(Permanent link til denne timeseddel)'
10
+ timesheet_group_by: Grupp&eacute;r efter
@@ -0,0 +1,10 @@
1
+ de:
2
+ timesheet_title: Stundenzettel
3
+ timesheet_date_from_label: Von
4
+ timesheet_date_to_label: Bis
5
+ timesheet_project_label: Projekt
6
+ timesheet_activities_label: Aktivität
7
+ timesheet_users_label: Benutzer
8
+ timesheet_showing_users: 'zeige Zeit für: '
9
+ timesheet_permalink: '(Permalink zu diesem Stundenzettel)'
10
+ timesheet_group_by: Gruppiere nach
@@ -0,0 +1,10 @@
1
+ en:
2
+ timesheet_title: Timesheet
3
+ timesheet_date_from_label: From
4
+ timesheet_date_to_label: To
5
+ timesheet_project_label: Project
6
+ timesheet_activities_label: Activity
7
+ timesheet_users_label: Users
8
+ timesheet_showing_users: 'showing time for: '
9
+ timesheet_permalink: '(Permalink to this timesheet)'
10
+ timesheet_group_by: Group by
@@ -0,0 +1,9 @@
1
+ es:
2
+ timesheet_title: Hoja de tiempos
3
+ timesheet_date_from_label: De
4
+ timesheet_date_to_label: A
5
+ timesheet_project_label: Proyecto
6
+ timesheet_activities_label: Actividad
7
+ timesheet_users_label: Usuarios
8
+ timesheet_showing_users: 'mostrando tiempos para: '
9
+ timesheet_permalink: '(Permalink a esta hoja de tiempos)'