From f72b042a6e48e51cd5ce19ffd835b6559fe18bc7 Mon Sep 17 00:00:00 2001
From: philippe L <philha@worteks.com>
Date: Thu, 18 Jan 2024 17:32:22 +0100
Subject: Group custom fields by category layout - Prototype - #39530

- WARNING : initial prototype

- add in custom field a colum with text referencing another custom field
  to be used at display to group them.
- when editing issues group custom fields if they have same group_category_layout
- topic display is based on custom field corresponding to grourp_category_layout text
  ( lazzy binding ).
---
 app/helpers/custom_fields_helper.rb           | 41 +++++++++++++++++++
 app/models/custom_field.rb                    |  3 +-
 app/views/custom_fields/_form.html.erb        |  2 +
 app/views/custom_fields/index.api.rsb         |  1 +
 app/views/issues/_form_custom_fields.html.erb | 22 ++++++++++
 config/locales/en.yml                         |  1 +
 config/locales/fr.yml                         |  1 +
 ...add_custom_fields_group_category_layout.rb | 10 +++++
 public/stylesheets/application.css            |  4 ++
 9 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 db/migrate/202311091914_add_custom_fields_group_category_layout.rb

diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb
index 435634df8..1348664be 100644
--- a/app/helpers/custom_fields_helper.rb
+++ b/app/helpers/custom_fields_helper.rb
@@ -227,4 +227,45 @@ module CustomFieldsHelper
       end
     end.join("\n").html_safe
   end
+
+   # Return an array of custom fields for which we want id.
+  def group_category_layout_for_select(selected_custom_field)
+    custom_fields=CustomField.all
+    select='<select name="custom_field[group_category_layout]" id="group_category_layout">'
+    select+='<option value=""'
+    if selected_custom_field.group_category_layout == ''
+      select+=' selected="true"'
+    end
+    select+=">None</option>\n"
+    custom_fields.each do |custom_field|
+      id_str=String(custom_field.id)
+      select +='<option value="' + id_str
+      if id_str == selected_custom_field.group_category_layout
+        select += '" selected="true'
+      end
+      select += '">' + custom_field.name + "</option>\n"
+    end
+    select+='</select>'
+    select.html_safe
+  end
+
+  def group_category_layout_custom_field(identifier)
+    if /\A\d+\z/.match(identifier)
+      @custom_field =  CustomField.find(Integer(identifier))
+    end
+    rescue ActiveRecord::RecordNotFound
+      nil
+  end
+
+  def group_category_layout_display_name(identifier)
+    @custom_field = group_category_layout_custom_field(identifier)
+    @custom_field ? @custom_field.name : identifier
+  end
+
+  def group_category_layout_description(identifier)
+    @custom_field = group_category_layout_custom_field(identifier)
+    title = @custom_field ? @custom_field.description.presence : nil
+    title ? title : identifier
+  end
+
 end
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index be9cf550f..e7e465c55 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -100,7 +100,8 @@ class CustomField < ActiveRecord::Base
     'user_role',
     'version_status',
     'extensions_allowed',
-    'full_width_layout')
+    'full_width_layout',
+    'group_category_layout')
 
   def copy_from(arg, options={})
     return if arg.blank?
diff --git a/app/views/custom_fields/_form.html.erb b/app/views/custom_fields/_form.html.erb
index a7af54a07..0a02a896c 100644
--- a/app/views/custom_fields/_form.html.erb
+++ b/app/views/custom_fields/_form.html.erb
@@ -7,6 +7,8 @@
 <p><%= f.text_field :name, :size => 50, :required => true %></p>
 <p><%= f.text_area :description, :rows => 7 %></p>
 
+<p><label for="group_category_layout"><%= l(:field_group_category_layout) %></label><%= group_category_layout_for_select(@custom_field) %></p>
+
 <% if @custom_field.format.multiple_supported %>
 <p>
   <%= f.check_box :multiple %>
diff --git a/app/views/custom_fields/index.api.rsb b/app/views/custom_fields/index.api.rsb
index 9f46f89f2..2ace7f01a 100644
--- a/app/views/custom_fields/index.api.rsb
+++ b/app/views/custom_fields/index.api.rsb
@@ -15,6 +15,7 @@ api.array :custom_fields do
       api.multiple          field.multiple?
       api.default_value     field.default_value
       api.visible           field.visible?
+      api.group_catgory_layout field.group_category_layout?
 
       values = field.possible_values_options
       if values.present?
diff --git a/app/views/issues/_form_custom_fields.html.erb b/app/views/issues/_form_custom_fields.html.erb
index fddcd742f..3a0103ce8 100644
--- a/app/views/issues/_form_custom_fields.html.erb
+++ b/app/views/issues/_form_custom_fields.html.erb
@@ -1,6 +1,10 @@
 <% 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 %>
+<% custom_field_values_group_category_layout = custom_field_values.select { |value| value.custom_field.group_category_layout? } %>
+<% custom_field_values -= custom_field_values_group_category_layout %>
+<% custom_field_values_group_category_layout = custom_field_values_group_category_layout.sort_by { |value| [value.custom_field.group_category_layout] } %>
+
 
 <% if custom_field_values.present? %>
 <div class="splitcontent">
@@ -18,6 +22,24 @@
 </div>
 <% end %>
 
+<% group_category_layout = '' %>
+
+<% custom_field_values_group_category_layout.each do |value| %>
+<% if group_category_layout != value.custom_field.group_category_layout -%>
+  <% if group_category_layout != '' -%>
+  </fieldset>
+<% end -%>
+  <% group_category_layout = value.custom_field.group_category_layout %>
+<fieldset class="group_category_layout" >
+  <legend>
+    <span title="<%= group_category_layout_description group_category_layout %>" class="field-description"><%= group_category_layout_display_name group_category_layout %>
+    </span>
+  </legend>
+<% end -%>  
+    <p><%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %></p>
+  <% end %>
+</fieldset>
+
 <% custom_field_values_full_width.each do |value| %>
   <p><%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %></p>
   <%= wikitoolbar_for "issue_custom_field_values_#{value.custom_field_id}", preview_issue_path(:project_id => @issue.project, :issue_id => @issue.id) if value.custom_field.full_text_formatting? %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2bbd776a3..b53c68368 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1414,3 +1414,4 @@ en:
   text_user_destroy_confirmation: "Are you sure you want to delete this user and remove all references to them? This cannot be undone. Often, locking a user instead of deleting them is the better solution. To confirm, please enter their login (%{login}) below."
   text_project_destroy_enter_identifier: "To confirm, please enter the project's identifier (%{identifier}) below."
   field_name_or_email_or_login: Name, email or login
+  field_group_category_layout: Group Category Layout
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index ac8a30dce..8613d8a10 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -1468,3 +1468,4 @@ fr:
   text_default_active_job_queue_changed: Gestionnaire de file par défaut le plus adapté
   label_user_mail_option_bookmarked: For any event on my bookmarked projects
   label_option_auto_lang: auto
+  field_group_category_layout: Groupe de présentation
diff --git a/db/migrate/202311091914_add_custom_fields_group_category_layout.rb b/db/migrate/202311091914_add_custom_fields_group_category_layout.rb
new file mode 100644
index 000000000..07d2f19ad
--- /dev/null
+++ b/db/migrate/202311091914_add_custom_fields_group_category_layout.rb
@@ -0,0 +1,10 @@
+class AddCustomFieldsGroupCategoryLayout < ActiveRecord::Migration[6.1]
+  def up
+    add_column :custom_fields, :group_category_layout, :text
+  end
+
+  def down
+    remove_column :custom_fields, :group_category_layout
+  end
+end
+
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 2015768d0..80668d60e 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -568,6 +568,9 @@ p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
 .wiki-class-ltr {direction:ltr !important;}
 .wiki-class-rtl {direction:rtl !important;}
 
+div.group_category_layout { border-style:solid; border-width:thin; border-color: #ccc}
+fieldset.group_category_layout { border-style:solid; border-width:thin; border-color: #ccc}
+
 div.issue div.subject div div { padding-left: 16px; word-break: break-word; }
 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
 div.issue div.subject>div>p { margin-top: 0.5em; }
@@ -582,6 +585,7 @@ div.issue .attribute .value {overflow:auto; text-overflow: ellipsis;}
 div.issue .attribute.string_cf .value .wiki p {margin-top: 0; margin-bottom: 0;}
 div.issue .attribute.text_cf .value .wiki p:first-of-type {margin-top: 0;}
 div.issue.overdue .due-date .value { color: #c22; }
+
 body.controller-issues h2.inline-flex {padding-right: 0}
 
 #issue_tree table.issues, #relations table.issues { border: 0; }
-- 
2.34.1

