Commit 872a3a0db055a8f39388f2bc7951f77feef2f772

Authored by Austin Smith
1 parent 783059bace
Exists in master

per #18, change config screen to allow admin to set vertical order of statuses

Showing 9 changed files with 128 additions and 67 deletions Side-by-side Diff

app/controllers/taskboard_controller.rb View file @ 872a3a0
... ... @@ -66,14 +66,13 @@
66 66 column.weight = new_state[:weight].to_i
67 67 column.max_issues = new_state[:max_issues].to_i
68 68 column.save!
69   - column.issue_statuses.clear()
  69 + column.status_buckets.clear()
70 70 end
71   - params[:status].each do |status_id, column_id|
72   - status_id = status_id.to_i
73   - column_id = column_id.to_i
74   - unless column_id == 0
75   - @columns = TaskBoardColumn.find(column_id)
76   - @columns.issue_statuses << IssueStatus.find(status_id)
  71 + params[:status].each do |column_id, statuses|
  72 + statuses.each do |status_id, weight|
  73 + status_id = status_id.to_i
  74 + column_id = column_id.to_i
  75 + StatusBucket.create!(:task_board_column_id => column_id, :issue_status_id => status_id, :weight => weight)
77 76 end
78 77 end
79 78 render 'settings/update'
app/models/status_bucket.rb View file @ 872a3a0
  1 +class StatusBucket < ActiveRecord::Base
  2 + default_scope order('weight ASC')
  3 + belongs_to :issue_status
  4 + belongs_to :task_board_column
  5 + unloadable
  6 +end
app/models/task_board_column.rb View file @ 872a3a0
1 1 class TaskBoardColumn < ActiveRecord::Base
2 2 unloadable
3 3 belongs_to :project
4   - has_and_belongs_to_many :issue_statuses
  4 + has_many :status_buckets
  5 + has_many :issue_statuses, :through => :status_buckets
5 6 validates_presence_of :title, :project
6 7 validates_length_of :title, :maximum => 255
7 8  
8   - def self.empty_status(status_id)
  9 + def self.empty_status(project_id, status_id)
9 10 columns = TaskBoardColumn \
10 11 .select(:id) \
11   - .joins('INNER JOIN issue_statuses_task_board_columns istbc ON istbc.task_board_column_id = task_board_columns.id') \
12   - .where('istbc.issue_status_id = ?', status_id)
13   - puts columns
  12 + .joins('INNER JOIN status_buckets b ON b.task_board_column_id = task_board_columns.id') \
  13 + .where('b.issue_status_id = ? AND project_id = ?', status_id, project_id)
14 14 return columns.empty?
15 15 end
16 16  
17 17 def issues(order_column="project_weight")
18 18 @column_statuses = Hash.new
19   - self.issue_statuses.order(:name).each do |status|
  19 + self.issue_statuses.order(:weight).each do |status|
20 20 @column_statuses[status.id] = Array.new
21 21 issues = Issue.select("issues.*, tbi.is_archived, tbi.#{order_column} as weight, tbi.issue_id") \
22 22 .joins('LEFT OUTER JOIN task_board_issues AS tbi ON tbi.issue_id = issues.id') \
app/views/settings/_column_manager.html.erb View file @ 872a3a0
... ... @@ -7,59 +7,54 @@
7 7 <% end %>
8 8  
9 9 <%= form_tag project_taskboard_columns_update_path(:project_id => @project.id), :method => 'put', :remote => true do %>
10   - <ul class="cant-move">
11   - <li class="head"><p class="cell"><%= translate :task_board_statuses %></p></li>
12   - <% statuses.each do |issue_status| %>
13   - <li><%= issue_status.name %></li>
14   - <% end %>
15   - </ul>
16   -
17   - <ul class="cant-move">
18   - <li class="head"><p class="cell"><%= translate :task_board_not_shown %></p></li>
19   - <% statuses.each do |issue_status| %>
20   - <li class="has-radio">
21   - <%= radio_button_tag("status[#{issue_status.id}]", 0, TaskBoardColumn.empty_status(issue_status.id)) %>
22   - </li>
23   - <% end %>
24   - </ul>
25   -
  10 + <div class="status-bucket column">
  11 + <h5>Available Statuses</h5>
  12 + <p><em>Configure the taskboard columns by dragging statuses to them.</em></p>
  13 + <ul class="buckets-wrapper status-holder">
  14 + <% statuses.each do |status| %>
  15 + <% if TaskBoardColumn.empty_status(@project.id, status.id) %>
  16 + <li class="status-pill" data-status-id="<%= status.id.to_s %>"><%= status.name %></li>
  17 + <% end %>
  18 + <% end %>
  19 + </ul>
  20 + </div>
26 21 <div class="movable-columns" id="taskboard-columns">
27   - <h4><%= translate :task_board_visible_columns %></h4>
28 22 <% columns.each do |column| %>
29   - <ul>
30   - <li class="head"><p class="cell"><%= column.title %></p></li>
31   - <% statuses.each do |issue_status| %>
32   - <li class="has-radio">
33   - <%= radio_button_tag("status[#{issue_status.id}]", column.id, column.issue_status_ids.include?(issue_status.id)) %>
34   - </li>
35   - <% end %>
36   - <li><%= link_to(
37   - l(:button_delete),
38   - project_taskboard_columns_delete_path(:project_id => @project.id, :column_id => column.id),
39   - :method => :delete,
40   - :confirm => l(:text_are_you_sure),
41   - :remote => true,
42   - :class => 'icon icon-del'
43   - )
44   - %>
45   - </li>
46   - <li class="column-options">
  23 + <div class="column dyn-column" data-column-id="<%= column.id.to_s %>">
  24 + <div class="input-wrapper"></div>
  25 + <h5><%= column.title %></h5>
  26 + <div class="column-meta">
  27 + <%= link_to(
  28 + l(:button_delete),
  29 + project_taskboard_columns_delete_path(:project_id => @project.id, :column_id => column.id),
  30 + :method => :delete,
  31 + :confirm => l(:text_are_you_sure),
  32 + :remote => true,
  33 + :class => 'icon icon-del'
  34 + )
  35 + %>
47 36 <p class="cell">
48 37 <%= label_tag("column[#{column.id}][max_issues]", translate(:task_board_task_limit)) %>
49 38 <%= text_field_tag("column[#{column.id}][max_issues]", column.max_issues, :class => 'column-max-issues') %>
50 39 <%= hidden_field_tag("column[#{column.id}][weight]", column.weight, :class => 'column-weight') %>
51 40 </p>
52   - </li>
53   - </ul>
  41 + </div>
  42 + <ul class="buckets-wrapper status-holder clearfix">
  43 + <% column.issue_statuses.each do |status| %>
  44 + <li class="status-pill" data-status-id="<%= status.id.to_s %>"><%= status.name %></li>
  45 + <% end %>
  46 + </ul>
  47 + </div>
54 48 <% end %>
55 49 </div>
56 50  
57 51 <div class="form-save">
58 52 <%= submit_tag(translate :task_board_save_changes) %>
59 53 </div>
60   -
61   - <script type="text/javascript">
62   - var taskboard_settings = new TaskBoardSettings('taskboard-columns', {tag: 'ul', items: '> ul:visible', handle: '.head', weightSelector: '.column-options .column-weight', axis: 'x'});
63   - </script>
64 54 <% end %>
  55 +
  56 +<script type="text/javascript">
  57 + var taskboard_columns = new TaskBoardSettings('taskboard-columns', {tag: 'div', items: '> div.column', handle: 'h5', weightSelector: '.column-options .column-weight', axis: 'x'});
  58 + var taskboard_statuses = new TaskBoardStatuses('status-holder', {connectWith: '.status-holder'});
  59 +</script>
app/views/settings/_project.html.erb View file @ 872a3a0
1 1 <%= javascript_include_tag('task_board', :plugin => 'redmine_task_board') %>
2 2  
3 3 <style>
  4 +.status-holder { list-style-type: none; margin: 0; padding: 0; }
  5 +.status-pill { list-style-type: none; cursor: move; float: left; background: #efefef; border: solid 1px #aaa; margin: 0 5px 5px 0; padding: 5px; border-radius: 3px; }
  6 +
4 7 #column_manager { overflow: auto }
5   -#column_manager ul { float: left; width: 110px; overflow: hidden; list-style-type: none; padding: 5px 0; margin: 0 3px 0 0; }
6   -#column_manager div.movable-columns { float: left; }
7 8 #column_manager div.movable-columns h4 { text-align: center; height: 15px; padding: 5px 0; margin: 0 0 5px 0; font-size: 15px; }
8   -#column_manager div.movable-columns ul { background: #ffd; }
9   -#column_manager div.movable-columns ul li.head { cursor: move; }
10   -#column_manager ul li { height: 22px; padding: 2px 4px 0 4px; width: 102px; text-align: center; }
11   -#column_manager ul li p.cell { display: table-cell; vertical-align: middle; height: 24px; text-align: center; width: 102px; padding: 0; margin: 0; }
12   -#column_manager ul li.head, #column_manager ul li.head p.cell { font-weight: bold; height: 60px; }
13   -#column_manager ul li:nth-child(even) { background: #ededed; }
14   -#column_manager ul li.column-options .column-max-issues { font-size: 11px; width: 22px; padding: 0; text-align: center; }
15   -#column_manager div.movable-columns ul li:nth-child(even) { background: #ff9; }
16   -#column_manager ul.cant-move { margin-top: 30px; }
  9 +#column_manager .column { float: left; width: 165px; margin-right: 10px; padding: 5px; }
  10 +#column_manager .column.dyn-column { background: #fafafa; border: solid 1px #ccc; }
  11 +#column_manager .column h5 { font-size: 16px; margin: 0 0 5px 0; padding: 0; cursor: move; }
  12 +#column_manager .column .buckets-wrapper { border: dashed 1px #ccc; padding: 5px; }
  13 +#column_manager .column .buckets-wrapper .status-pill { float: none; }
17 14 #column_manager div.form-save { clear: left; padding-top: 15px; }
  15 +
  16 +#column_manager ul.status-holder { width: 150px; }
  17 +p.cell input { width: 30px; }
  18 +.clearfix:before, .clearfix:after { content: ""; display: table; }
  19 +.clearfix:after { clear: both; }
  20 +.clearfix { zoom: 1; }
18 21 </style>
19 22  
20 23 <%= translate :task_board_help %>
assets/javascripts/task_board.js View file @ 872a3a0
... ... @@ -307,4 +307,29 @@
307 307 }
308 308  
309 309 });
  310 +
  311 +var TaskBoardStatuses = TaskBoardSortable.extend({
  312 + init: function(cls, options) {
  313 + this.cls = cls;
  314 + this.options = options;
  315 + this.options.update = this.setInputs;
  316 + this.root = $('.' + this.cls);
  317 + this.root.sortable(this.options);
  318 + this.setInputs();
  319 + },
  320 +
  321 + setInputs: function() {
  322 + $('div.dyn-column').each(function() {
  323 + var weight = 0,
  324 + column_id = $(this).data('column-id'),
  325 + $input_wrapper = $(this).find('div.input-wrapper');
  326 + $input_wrapper.empty();
  327 + $(this).find('.status-pill').each(function() {
  328 + $input_wrapper.append(
  329 + '<input type="hidden" name="status[' + column_id + '][' + $(this).data('status-id') + ']" value=' + (weight++) + '" />'
  330 + );
  331 + });
  332 + });
  333 + }
  334 +})
assets/stylesheets/taskboard.css View file @ 872a3a0
... ... @@ -215,4 +215,8 @@
215 215 #not-prioritized-wrapper li.card.minimized {
216 216 display: none;
217 217 }
  218 +
  219 +.clearfix:before, .clearfix:after { content: ""; display: table; }
  220 +.clearfix:after { clear: both; }
  221 +.clearfix { zoom: 1; }
db/migrate/004_create_status_buckets.rb View file @ 872a3a0
  1 +class CreateStatusBuckets < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :status_buckets do |t|
  4 + t.integer :issue_status_id
  5 + t.integer :task_board_column_id
  6 + t.integer :weight
  7 + end
  8 +
  9 + column_statuses = ActiveRecord::Base.connection.execute("SELECT issue_status_id, task_board_column_id FROM issue_statuses_task_board_columns ORDER BY issue_status_id, task_board_column_id")
  10 + weight = 0
  11 + column_statuses.each do |status|
  12 + StatusBucket.create!(:task_board_column_id => status[1], :issue_status_id => status[0], :weight => weight )
  13 + weight += 1
  14 + end
  15 +
  16 + # finally, dump the old hatbm associations
  17 + drop_table :issue_statuses_task_board_columns
  18 +
  19 + end
  20 +end
test/unit/status_bucket_test.rb View file @ 872a3a0
  1 +require File.expand_path('../../test_helper', __FILE__)
  2 +
  3 +class StatusBucketTest < ActiveSupport::TestCase
  4 +
  5 + # Replace this with your real tests.
  6 + def test_truth
  7 + assert true
  8 + end
  9 +end