diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a43038a..89a799a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -177,6 +177,10 @@ class ProjectsController < ApplicationController @version_status = params[:version_status] || 'open' @version_name = params[:version_name] @versions = @project.shared_versions.status(@version_status).like(@version_name).sorted + @cfs=AttributeGroup.joins(:custom_fields).joins(:tracker). + where(project_id: @project, tracker_id: @trackers, :custom_fields => {id: @project.all_issue_custom_fields.pluck(:id)}). + pluck("trackers.id", "id", "name", "position","attribute_group_fields.id", "attribute_group_fields.position", + "custom_fields.id", "custom_fields.name", "custom_fields.position").sort_by{|x| [x[3], x[5]]} end def edit @@ -203,6 +207,39 @@ class ProjectsController < ApplicationController end end + def groupissuescustomfields + # clean invalid values: invalid cfs, empty cf lists, empty groups + group_issues_custom_fields = (JSON.parse params[:group_issues_custom_fields]). + each{|tid,v| v.replace(v.select{|k,v| v["cfs"] ? v["cfs"].delete_if{|k,v| @project.all_issue_custom_fields.pluck(:id).include?(v)} : v})}. + each{|tid,v| v.delete_if{|k,v| v["cfs"].blank?}}. + delete_if{|k,v| v.blank?} + + groups = AttributeGroup.where(project_id: @project.id).collect(&:id) + fields = AttributeGroupField.where(attribute_group_id: groups).collect(&:id) + group_issues_custom_fields.each do |tid,v| + v.each do |gp, g| + gid = groups.shift + if gid.nil? + gid=AttributeGroup.create(project_id: @project.id, tracker_id: tid, name: g["name"].nil? ? nil : g["name"], position: gp).id + else + AttributeGroup.update(gid, project_id: @project.id, tracker_id: tid, name: g["name"].nil? ? nil : g["name"], position: gp) + end + g['cfs'].each do |cfp, cf| + cfid = fields.shift + if cfid.nil? + AttributeGroupField.create(attribute_group_id: gid, custom_field_id: cf, position: cfp) + else + AttributeGroupField.update(cfid, attribute_group_id: gid, custom_field_id: cf, position: cfp) + end + end + end + end + AttributeGroupField.where(id: fields).delete_all + AttributeGroup.where(id: groups).destroy_all + flash[:notice] = l(:notice_successful_update) + redirect_to settings_project_path(@project, :tab => 'groupissuescustomfields') + end + def archive unless @project.archive flash[:error] = l(:error_can_not_archive_project) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index aee8444..25529a0 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -227,41 +227,53 @@ module IssuesHelper r.to_html end - def render_half_width_custom_fields_rows(issue) - values = issue.visible_custom_field_values.reject {|value| value.custom_field.full_width_layout?} - return if values.empty? - half = (values.size / 2.0).ceil - issue_fields_rows do |rows| - values.each_with_index do |value, i| - css = "cf_#{value.custom_field.id}" - attr_value = show_value(value) - if value.custom_field.text_formatting == 'full' - attr_value = content_tag('div', attr_value, class: 'wiki') - end - m = (i < half ? :left : :right) - rows.send m, custom_field_name_tag(value.custom_field), attr_value, :class => css - end - end + def group_by_keys(project_id, tracker_id, custom_field_values) + keys_grouped = AttributeGroupField.joins(:attribute_group). + where(:attribute_groups => {project_id: project_id, tracker_id: tracker_id}). + order("attribute_groups.position", :position).pluck(:name, :custom_field_id).group_by(&:shift) + custom_fields_grouped = { nil => (keys_grouped[nil].nil? ? [] : keys_grouped[nil].map{|n| custom_field_values.select{|x| x.custom_field[:id] == n[0]}}.flatten) | + custom_field_values.select{|y| ! keys_grouped.values.flatten.include?(y.custom_field[:id])}} + keys_grouped.reject{|k,v| k == nil}.each{|k,v| custom_fields_grouped[k] = v.map{|n| custom_field_values.select{|x| x.custom_field[:id] == n[0]}}.flatten} + custom_fields_grouped end - def render_full_width_custom_fields_rows(issue) - values = issue.visible_custom_field_values.select {|value| value.custom_field.full_width_layout?} - return if values.empty? - + def render_custom_fields_rows(issue) s = ''.html_safe - values.each_with_index do |value, i| - attr_value = show_value(value) - next if attr_value.blank? - - if value.custom_field.text_formatting == 'full' - attr_value = content_tag('div', attr_value, class: 'wiki') + group_by_keys(issue.project_id, issue.tracker_id, issue.visible_custom_field_values).each do |title, values| + if values.present? + s += content_tag('h4', title, :style => 'background: #0001; padding: 0.3em;') unless title.nil? + while values.present? + if values[0].custom_field.full_width_layout? + while values.present? && values[0].custom_field.full_width_layout? + value=values.shift + attr_value = show_value(value) + if value.custom_field.text_formatting == 'full' + attr_value = content_tag('div', attr_value, class: 'wiki') + end + content = content_tag('div', custom_field_name_tag(value.custom_field) + ":", :class => 'label') + + content_tag('div', attr_value, :class => 'value') + content = content_tag('div', content, :class => "cf_#{value.custom_field.id} attribute") + s += content_tag('div', content, :class => 'splitcontent') + end + else + lr_values = [] + while values.present? && ! values[0].custom_field.full_width_layout? + lr_values += [ values.shift ] + end + half = (lr_values.size / 2.0).ceil + s += issue_fields_rows do |rows| + lr_values.each_with_index do |value, i| + attr_value = show_value(value) + if value.custom_field.text_formatting == 'full' + attr_value = content_tag('div', attr_value, class: 'wiki') + end + m = (i < half ? :left : :right) + rows.send m, custom_field_name_tag(value.custom_field), attr_value, :class => "cf_#{value.custom_field.id}" + end + end + end + end end - - content = - content_tag('hr') + - content_tag('p', content_tag('strong', custom_field_name_tag(value.custom_field) )) + - content_tag('div', attr_value, class: 'value') - s << content_tag('div', content, class: "cf_#{value.custom_field.id} attribute") end s end @@ -320,11 +332,15 @@ module IssuesHelper end end end - issue.visible_custom_field_values(user).each do |value| - if html - items << content_tag('strong', "#{value.custom_field.name}: ") + show_value(value, false) - else - items << "#{value.custom_field.name}: #{show_value(value, false)}" + group_by_keys(issue.project_id, issue.tracker_id, issue.visible_custom_field_values(user)).each do |title, values| + if values.present? + item = [ (html ? content_tag('strong', "#{title}") : "#{title}") ] unless title.nil? + values.each do |value| + (title.nil? ? items : item) << (html ? + content_tag('strong', "#{value.custom_field.name}: ") + show_value(value, false) : + "#{value.custom_field.name}: #{show_value(value, false)}") + end + items << item unless title.nil? end end items @@ -333,9 +349,12 @@ module IssuesHelper def render_email_issue_attributes(issue, user, html=false) items = email_issue_attributes(issue, user, html) if html - content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe, :class => "details") + content_tag('ul', items.select{|s| s.is_a? String}.map{|s| content_tag('li', s)}.join("\n").html_safe, :class => "details") + "\n" + + items.select{|s| !s.is_a? String}.map{|item| content_tag('div', item.shift) + "\n" + + content_tag('ul', item.map{|s| content_tag('li', s)}.join("\n").html_safe, :class => "details")}.join("\n").html_safe else - items.map{|s| "* #{s}"}.join("\n") + items.select{|s| s.is_a? String}.map{|s| "* #{s}"}.join("\n") + "\n" + + items.select{|s| !s.is_a? String}.map{|item| "#{item.shift}\n" + item.map{|s| "* #{s}"}.join("\n")}.join("\n") end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7945461..42124f1 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -27,7 +27,8 @@ module ProjectsHelper {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, {:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, - {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :label_time_tracking} + {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :label_time_tracking}, + {:name => 'groupissuescustomfields', :action => :edit_project, :partial => 'projects/settings/groupissuescustomfields', :label => :grouped_cf} ] tabs. select {|tab| User.current.allowed_to?(tab[:action], @project)}. diff --git a/app/models/attribute_group.rb b/app/models/attribute_group.rb new file mode 100644 index 0000000..52d7ac3 --- /dev/null +++ b/app/models/attribute_group.rb @@ -0,0 +1,9 @@ +class AttributeGroup < ActiveRecord::Base + belongs_to :project + belongs_to :tracker + has_many :attribute_group_fields, :dependent => :delete_all + has_many :custom_fields, :through => :attribute_group_fields + acts_as_positioned + + scope :sorted, lambda { order(:position) } +end diff --git a/app/models/attribute_group_field.rb b/app/models/attribute_group_field.rb new file mode 100644 index 0000000..c040138 --- /dev/null +++ b/app/models/attribute_group_field.rb @@ -0,0 +1,7 @@ +class AttributeGroupField < ActiveRecord::Base + belongs_to :attribute_group + belongs_to :custom_field + acts_as_positioned + + scope :sorted, lambda { order(:position) } +end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 008ef49..41293a4 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -24,6 +24,7 @@ class CustomField < ActiveRecord::Base :class_name => 'CustomFieldEnumeration', :dependent => :delete_all has_many :custom_values, :dependent => :delete_all + has_many :attribute_group_fields, :dependent => :delete_all has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id" acts_as_positioned serialize :possible_values diff --git a/app/models/project.rb b/app/models/project.rb index 6d91d57..2888a9e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -51,6 +51,8 @@ class Project < ActiveRecord::Base has_many :changesets, :through => :repository has_one :wiki, :dependent => :destroy # Custom field for the project issues + has_many :attribute_groups, :dependent => :destroy + has_many :attribute_group_fields, :through => :attribute_groups has_and_belongs_to_many :issue_custom_fields, lambda {order(:position)}, :class_name => 'IssueCustomField', diff --git a/app/models/tracker.rb b/app/models/tracker.rb index d90ab3e..a4e9d79 100644 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -30,6 +30,8 @@ class Tracker < ActiveRecord::Base has_many :workflow_rules, :dependent => :delete_all has_and_belongs_to_many :projects has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' + has_many :attribute_groups, :dependent => :destroy + has_many :attribute_group_fields, :through => :attribute_groups acts_as_positioned validates_presence_of :default_status diff --git a/app/views/issues/_form_custom_fields.html.erb b/app/views/issues/_form_custom_fields.html.erb index 13bedd5..f2991e8 100644 --- a/app/views/issues/_form_custom_fields.html.erb +++ b/app/views/issues/_form_custom_fields.html.erb @@ -1,23 +1,31 @@ -<% custom_field_values = @issue.editable_custom_field_values %> -<% custom_field_values_full_width = custom_field_values.select { |value| value.custom_field.full_width_layout? } %> -<% custom_field_values -= custom_field_values_full_width %> - -<% if custom_field_values.present? %> +<% group_by_keys(@issue.project_id, @issue.tracker_id, @issue.editable_custom_field_values).each do |title,values| %> +<% if values.present? %> +<%= content_tag('h4', title, :style => 'background: #0001; padding: 0.3em;') unless title.nil? %> +<% while values.present? %>
+<% if values[0].custom_field.full_width_layout? %> +<% while values.present? && values[0].custom_field.full_width_layout? %> +<% value = values.shift %> +

<%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %>

+<% end %> +<% else %> +<% lr_values = [] %> +<% while values.present? && ! values[0].custom_field.full_width_layout? %> +<% lr_values += [ values.shift ] %> +<% end %>
<% i = 0 %> -<% split_on = (custom_field_values.size / 2.0).ceil - 1 %> -<% custom_field_values.each do |value| %> -

<%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %>

-<% if i == split_on -%> +<% split_on = (lr_values.size / 2.0).ceil - 1 %> +<% lr_values.each do |value| %> +

<%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %>

+<% if i == split_on %>
-<% end -%> -<% i += 1 -%> -<% end -%> +<% end %> +<% i += 1 %> +<% end %>
+<% end %>
<% end %> - -<% custom_field_values_full_width.each do |value| %> -

<%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %>

+<% end %> <% end %> diff --git a/app/views/issues/show.html.erb b/app/views/issues/show.html.erb index 5a1cc85..244fee2 100644 --- a/app/views/issues/show.html.erb +++ b/app/views/issues/show.html.erb @@ -71,7 +71,7 @@ rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time' end end %> -<%= render_half_width_custom_fields_rows(@issue) %> +<%= render_custom_fields_rows(@issue) %> <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> @@ -94,8 +94,6 @@ end %> <%= link_to_attachments @issue, :thumbnails => true %> <% end %> -<%= render_full_width_custom_fields_rows(@issue) %> - <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %> <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %> diff --git a/app/views/projects/settings/_groupissuescustomfields.html.erb b/app/views/projects/settings/_groupissuescustomfields.html.erb new file mode 100644 index 0000000..58f8318 --- /dev/null +++ b/app/views/projects/settings/_groupissuescustomfields.html.erb @@ -0,0 +1,140 @@ +

<%= select_tag "tracker_id", options_from_collection_for_select(@project.trackers.collect, "id", "name"), {:required => true, :onchange => "refresh_trackers(this);"} %>

+ +

+ <% @project.trackers.each do |t| %> +
+ <%= l(:changed_cf_position) %> + + <%= l(:global_cf_position) %> + + <%= l(:grouped_cf) %> +
+ <% @cfs.select{|x| x[0]==t.id && x[2]!=nil}.map{|x| x[2]}.uniq.each do |g| %> +
+ + +
    + <% @cfs.select{|x| x[0]==t.id && x[2]==g}.each do |x| %> +
  • <%= x[7] %>
  • + <% end %> +
+
+ <% end %> +
+ + +
+
+
+ <% end %> +<%= form_for @project, :url => { :action => 'groupissuescustomfields', :id => @project }, + :html => {:id => 'groupissuescustomfields-form', + :method => :post} do |f| %> + +<%= hidden_field :group_issues_custom_fields, '', :id => 'group_issues_custom_fields', :name => 'group_issues_custom_fields' %> +

<%= submit_tag l(:button_save), :onclick => "fill_json_data();" %>

+<% end %> +
+ + diff --git a/config/locales/en.yml b/config/locales/en.yml index 36d2099..66cf55d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1223,6 +1223,9 @@ en: description_issue_category_reassign: Choose issue category description_wiki_subpages_reassign: Choose new parent page text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed.' + grouped_cf: Grouped Custom Fields + global_cf_position: Global Custom Fields Position + changed_cf_position: Changed Custom Fields Position text_login_required_html: When not requiring authentication, public projects and their contents are openly available on the network. You can edit the applicable permissions. label_login_required_yes: "Yes" label_login_required_no: "No, allow anonymous access to public projects" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 1dcf52b..0b9d445 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1239,6 +1239,9 @@ pt-BR: permission_view_news: Ver notícias label_no_preview_alternative_html: Visualização não disponível. Faça o %{link} do arquivo. label_no_preview_download: download + grouped_cf: Agrupar campos personalizados + global_cf_position: Posição global de campos personalizados + changed_cf_position: Alterar posição de campos personalizados setting_close_duplicate_issues: Fechar tarefas duplicadas automaticamente error_exceeds_maximum_hours_per_day: Não é possível registrar mais de %{max_hours} horas no mesmo dia (%{logged_hours} horas já foram registradas) setting_time_entry_list_defaults: Registro de horas padrão diff --git a/config/routes.rb b/config/routes.rb index 0344982..9e27382 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -105,6 +105,7 @@ Rails.application.routes.draw do member do get 'settings(/:tab)', :action => 'settings', :as => 'settings' + post 'groupissuescustomfields' post 'archive' post 'unarchive' post 'close' diff --git a/db/migrate/20180913211420_create_attribute_groups.rb b/db/migrate/20180913211420_create_attribute_groups.rb new file mode 100644 index 0000000..e878f01 --- /dev/null +++ b/db/migrate/20180913211420_create_attribute_groups.rb @@ -0,0 +1,12 @@ +class CreateAttributeGroups < ActiveRecord::Migration[4.2] + def change + create_table :attribute_groups do |t| + t.references :project, index: true, foreign_key: true + t.references :tracker, index: true, foreign_key: true + t.string :name + t.integer :position, :default => 1, :null => false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20180913212008_create_attribute_group_fields.rb b/db/migrate/20180913212008_create_attribute_group_fields.rb new file mode 100644 index 0000000..8f44a38 --- /dev/null +++ b/db/migrate/20180913212008_create_attribute_group_fields.rb @@ -0,0 +1,11 @@ +class CreateAttributeGroupFields < ActiveRecord::Migration[4.2] + def change + create_table :attribute_group_fields do |t| + t.references :attribute_group, index: true, foreign_key: true + t.references :custom_field, index: true, foreign_key: true + t.integer :position + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20190301162408_change_group_position_attributes.rb b/db/migrate/20190301162408_change_group_position_attributes.rb new file mode 100644 index 0000000..3b7ae98 --- /dev/null +++ b/db/migrate/20190301162408_change_group_position_attributes.rb @@ -0,0 +1,6 @@ +class ChangeGroupPositionAttributes < ActiveRecord::Migration[4.2] + def change + change_column_null :attribute_groups, :position, true + change_column_default :attribute_groups, :position, nil + end +end diff --git a/lib/redmine.rb b/lib/redmine.rb index 6e7d109..3ec4221 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -77,7 +77,7 @@ Redmine::AccessControl.map do |map| map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true map.permission :search_project, {:search => :index}, :public => true, :read => true map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin - map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member + map.permission :edit_project, {:projects => [:settings, :edit, :update, :groupissuescustomfields]}, :require => :member map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :view_members, {:members => [:index, :show]}, :public => true, :read => true diff --git a/lib/redmine/export/pdf/issues_pdf_helper.rb b/lib/redmine/export/pdf/issues_pdf_helper.rb index 7e2c8a8..2621c3a 100644 --- a/lib/redmine/export/pdf/issues_pdf_helper.rb +++ b/lib/redmine/export/pdf/issues_pdf_helper.rb @@ -45,21 +45,21 @@ module Redmine pdf.SetFontStyle('',8) pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") pdf.ln - + left = [] left << [l(:field_status), issue.status] left << [l(:field_priority), issue.priority] left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') - + right = [] right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) - + rows = left.size > right.size ? left.size : right.size while left.size < rows left << nil @@ -68,12 +68,6 @@ module Redmine right << nil end - custom_field_values = issue.visible_custom_field_values.reject {|value| value.custom_field.full_width_layout?} - half = (custom_field_values.size / 2.0).ceil - custom_field_values.each_with_index do |custom_value, i| - (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] - end - if pdf.get_rtl border_first_top = 'RT' border_last_top = 'LT' @@ -85,7 +79,8 @@ module Redmine border_first = 'L' border_last = 'R' end - + border_middle_top = 'T' + rows = left.size > right.size ? left.size : right.size rows.times do |i| heights = [] @@ -100,26 +95,88 @@ module Redmine item = right[i] heights << pdf.get_string_height(60, item ? item.last.to_s : "") height = heights.max - + item = left[i] pdf.SetFontStyle('B',9) pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) pdf.SetFontStyle('',9) - pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0) - + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_middle_top : ""), '', 0, 0) + item = right[i] pdf.SetFontStyle('B',9) - pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_middle_top : ""), '', 0, 0) pdf.SetFontStyle('',9) pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2) - + pdf.set_x(base_x) end - + + group_by_keys(issue.project_id, issue.tracker_id, issue.visible_custom_field_values).each do |title, values| + if values.present? + unless title.nil? + pdf.RDMCell(35+155, 5, title, "LRT", 1) + end + + while values.present? + if values[0].custom_field.full_width_layout? + while values.present? && values[0].custom_field.full_width_layout? + value = values.shift + pdf.SetFontStyle('B',9) + pdf.RDMCell(35, 5, "#{value.custom_field.name}:", 'L', 0) + pdf.SetFontStyle('',9) + pdf.RDMCell(155, 5, show_value(value, false).to_s, 'R', 1) + end + else + lr_values = [] + while values.present? && ! values[0].custom_field.full_width_layout? + lr_values += [ values.shift ] + end + + half = (lr_values.size / 2.0).ceil + left = [] + right = [] + lr_values.each_with_index do |custom_value, i| + (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] + end + + rows = left.size > right.size ? left.size : right.size + rows.times do |i| + heights = [] + pdf.SetFontStyle('B',9) + item = left[i] + heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") + item = right[i] + heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") + pdf.SetFontStyle('',9) + item = left[i] + heights << pdf.get_string_height(60, item ? item.last.to_s : "") + item = right[i] + heights << pdf.get_string_height(60, item ? item.last.to_s : "") + height = heights.max + + item = left[i] + pdf.SetFontStyle('B',9) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", border_first, '', 0, 0) + pdf.SetFontStyle('',9) + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", "", '', 0, 0) + + item = right[i] + pdf.SetFontStyle('B',9) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", "", '', 0, 0) + pdf.SetFontStyle('',9) + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", border_last, '', 0, 2) + + pdf.set_x(base_x) + end + end + end + end + end + pdf.SetFontStyle('B',9) pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) pdf.SetFontStyle('',9) - + # Set resize image scale pdf.set_image_scale(1.6) text = textilizable(issue, :description, @@ -130,17 +187,6 @@ module Redmine ) pdf.RDMwriteFormattedCell(35+155, 5, '', '', text, issue.attachments, "LRB") - custom_field_values = issue.visible_custom_field_values.select {|value| value.custom_field.full_width_layout?} - custom_field_values.each do |value| - text = show_value(value, false) - next if text.blank? - - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155, 5, value.custom_field.name, "LRT", 1) - pdf.SetFontStyle('',9) - pdf.RDMwriteHTMLCell(35+155, 5, '', '', text, issue.attachments, "LRB") - end - unless issue.leaf? truncate_length = (!is_cjk? ? 90 : 65) pdf.SetFontStyle('B',9) @@ -157,7 +203,7 @@ module Redmine pdf.ln end end - + relations = issue.relations.select { |r| r.other_issue(issue).visible? } unless relations.empty? truncate_length = (!is_cjk? ? 80 : 60) @@ -185,7 +231,7 @@ module Redmine end pdf.RDMCell(190,5, "", "T") pdf.ln - + if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project) pdf.SetFontStyle('B',9) @@ -205,7 +251,7 @@ module Redmine pdf.ln end end - + if assoc[:journals].present? pdf.SetFontStyle('B',9) pdf.RDMCell(190,5, l(:label_history), "B") @@ -234,7 +280,7 @@ module Redmine pdf.ln end end - + if issue.attachments.any? pdf.SetFontStyle('B',9) pdf.RDMCell(190,5, l(:label_attachment_plural), "B") @@ -261,7 +307,7 @@ module Redmine pdf.footer_date = format_date(User.current.today) pdf.set_auto_page_break(false) pdf.add_page("L") - + # Landscape A4 = 210 x 297 mm page_height = pdf.get_page_height # 210 page_width = pdf.get_page_width # 297 @@ -269,7 +315,7 @@ module Redmine right_margin = pdf.get_original_margins['right'] # 10 bottom_margin = pdf.get_footer_margin row_height = 4 - + # column widths table_width = page_width - right_margin - left_margin col_width = [] @@ -277,13 +323,13 @@ module Redmine col_width = calc_col_width(issues, query, table_width, pdf) table_width = col_width.inject(0, :+) end - + # use full width if the description or last_notes are displayed if table_width > 0 && (query.has_column?(:description) || query.has_column?(:last_notes)) col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} table_width = col_width.inject(0, :+) end - + # title pdf.SetFontStyle('B',11) pdf.RDMCell(190, 8, title) @@ -317,10 +363,10 @@ module Redmine end previous_group = group end - + # fetch row values col_values = fetch_row_values(issue, query, level) - + # make new page if it doesn't fit on the current one base_y = pdf.get_y max_height = get_issues_to_pdf_write_cells(pdf, col_values, col_width) @@ -330,11 +376,11 @@ module Redmine render_table_header(pdf, query, col_width, row_height, table_width) base_y = pdf.get_y end - + # write the cells on page issues_to_pdf_write_cells(pdf, col_values, col_width, max_height) pdf.set_y(base_y + max_height) - + if query.has_column?(:description) && issue.description? pdf.set_x(10) pdf.set_auto_page_break(true, bottom_margin) @@ -349,7 +395,7 @@ module Redmine pdf.set_auto_page_break(false) end end - + if issues.size == Setting.issues_export_limit.to_i pdf.SetFontStyle('B',10) pdf.RDMCell(0, row_height, '...') diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 0f708bf..566c300 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -475,9 +475,10 @@ div.issue p.author {margin-top:0.5em;} div.issue span.private, div.journal span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;} div.issue .next-prev-links {color:#999;} div.issue .attributes {margin-top: 2em;} -div.issue .attributes .attribute {padding-left:180px; clear:left; min-height: 1.8em;} -div.issue .attributes .attribute .label {width: 170px; margin-left:-180px; font-weight:bold; float:left; overflow:hidden; text-overflow: ellipsis;} +div.issue .attributes .attribute {padding-left:225px; clear:left; min-height: 1.8em;} +div.issue .attributes .attribute .label {width: 215px; margin-left:-225px; font-weight:bold; float:left; overflow:hidden; text-overflow: ellipsis;} div.issue .attribute .value {overflow:auto; text-overflow: ellipsis;} +div.issue .attribute .value p {margin: 0 0 0.8em 0;} div.issue.overdue .due-date .value { color: #c22; } #issue_tree table.issues, #relations table.issues { border: 0; } @@ -1549,3 +1550,48 @@ img { max-height: 100%; max-width: 100%; } + +/* Custom Field Groups */ +.sortable_groups { + background: #eee; + border: 1px solid #888; + width: 445px; + min-height: 30px; + margin: 5px; + padding: 5px; +} +.sortable_groups div { + background: #ddf; + border: 1px solid #888; + min-height: 30px; + margin: 5px; + padding: 5px; +} +.sortable_items { + background: #eee; + border: 1px solid #888; + width: 400px; + min-height: 30px; + margin: 5px; + padding: 5px; +} +.sortable_items li { + background: #ffffff; + border: 1px solid #888; + margin: 5px; + padding: 5px; + list-style-type: none; + font-size: 1.2em; +} +.not_a_group { + padding: 5px 27px; +} +.ui-sortable-handle:before { + content:url('/images/reorder.png'); + margin-right: 5px; +} +.ui-sortable-placeholder { + background: #8888ff; + border: 1px solid #888; + visibility: visible; +} diff --git a/test/fixtures/attribute_group_fields.yml b/test/fixtures/attribute_group_fields.yml new file mode 100644 index 0000000..006a407 --- /dev/null +++ b/test/fixtures/attribute_group_fields.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + attribute_group_id: + custom_field_id: + position: 1 + +two: + attribute_group_id: + custom_field_id: + position: 1 diff --git a/test/fixtures/attribute_groups.yml b/test/fixtures/attribute_groups.yml new file mode 100644 index 0000000..7b7aed5 --- /dev/null +++ b/test/fixtures/attribute_groups.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + project_id: + tracker_id: + name: MyString + position: 1 + +two: + project_id: + tracker_id: + name: MyString + position: 1