timesheet_plugin 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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)'