From 35fad41a974322a5a3cfde22b4d7b2de53083cde Mon Sep 17 00:00:00 2001 From: Katsuya HIDAKA Date: Sun, 11 Oct 2026 13:50:18 +0900 Subject: Fix disappearing Gantt bars in responsive layouts and oversized progress segments at low percentages Restore visible project and version bars in responsive layouts and prevent oversized progress segments at low percentages. Keep date and progress width calculations in Ruby and border sizing in CSS, allowing future border changes without adjusting the calculations. --- app/assets/stylesheets/gantt.css | 20 +++++++----- lib/redmine/helpers/gantt.rb | 6 ++-- test/functional/gantts_controller_test.rb | 30 +++++++++--------- test/unit/lib/redmine/helpers/gantt_test.rb | 34 ++++++++++++--------- 4 files changed, 50 insertions(+), 40 deletions(-) diff --git a/app/assets/stylesheets/gantt.css b/app/assets/stylesheets/gantt.css index 345be731b..45f19952e 100644 --- a/app/assets/stylesheets/gantt.css +++ b/app/assets/stylesheets/gantt.css @@ -248,6 +248,7 @@ inset-inline-start: var(--gantt-task-start); inline-size: var(--gantt-task-width); block-size: 8px; + box-sizing: border-box; font-size: 0.8em; color: var(--oc-gray-6); padding: 0; @@ -277,16 +278,19 @@ } .gantt-task-late { + block-size: 10px; background: var(--oc-red-5) url(/task_late.png); border: 1px solid var(--oc-red-5); } .gantt-task-done { + block-size: 10px; background: var(--oc-green-7) url(/task_done.png); border: 1px solid var(--oc-green-7); } .gantt-task-todo { + block-size: 10px; background: var(--oc-gray-5) url(/task_todo.png); border: 1px solid var(--oc-gray-5); } @@ -294,12 +298,12 @@ .gantt-task-todo.gantt-task-parent { background: var(--oc-gray-6); border: 1px solid var(--oc-gray-6); - block-size: 3px; + block-size: 5px; } .gantt-task-late.gantt-task-parent, .gantt-task-done.gantt-task-parent { - block-size: 3px; + block-size: 5px; } .gantt-task.gantt-task-parent.gantt-task-marker { @@ -313,21 +317,21 @@ .gantt-row[data-gantt-row-type="version"] .gantt-task-late { background: var(--oc-red-5) url(/milestone_late.png); border: 1px solid var(--oc-red-5); - block-size: 2px; + block-size: 4px; margin-block-start: 3px; } .gantt-row[data-gantt-row-type="version"] .gantt-task-done { background: var(--oc-green-7) url(/milestone_done.png); border: 1px solid var(--oc-green-7); - block-size: 2px; + block-size: 4px; margin-block-start: 3px; } .gantt-row[data-gantt-row-type="version"] .gantt-task-todo { background: var(--oc-white) url(/milestone_todo.png); border: 1px solid var(--oc-white); - block-size: 2px; + block-size: 4px; margin-block-start: 3px; } @@ -342,21 +346,21 @@ .gantt-row[data-gantt-row-type="project"] .gantt-task-late { background: var(--oc-red-5) url(/milestone_late.png); border: 1px solid var(--oc-red-5); - block-size: 2px; + block-size: 4px; margin-block-start: 3px; } .gantt-row[data-gantt-row-type="project"] .gantt-task-done { background: var(--oc-green-7) url(/milestone_done.png); border: 1px solid var(--oc-green-7); - block-size: 2px; + block-size: 4px; margin-block-start: 3px; } .gantt-row[data-gantt-row-type="project"] .gantt-task-todo { background: var(--oc-white) url(/milestone_todo.png); border: 1px solid var(--oc-white); - block-size: 2px; + block-size: 4px; margin-block-start: 3px; } diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 46a2cc289..74d5cb139 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -931,7 +931,7 @@ module Redmine task_classes << 'gantt-task-parent' if object.is_a?(Issue) && !object.leaf? # Renders the task bar, with progress and late if coords[:bar_start] && coords[:bar_end] - width = coords[:bar_end] - coords[:bar_start] - 2 + width = coords[:bar_end] - coords[:bar_start] style = task_style(coords[:bar_start], width) todo_data = {} if object.is_a?(Issue) @@ -944,14 +944,14 @@ module Redmine :data => todo_data} output << view.content_tag(:div, ' '.html_safe, content_opt) if coords[:bar_late_end] - width = coords[:bar_late_end] - coords[:bar_start] - 2 + width = coords[:bar_late_end] - coords[:bar_start] style = task_style(coords[:bar_start], width) output << view.content_tag(:div, ' '.html_safe, :style => style, :class => (task_classes + ['gantt-task-late']).join(' ')) end if coords[:bar_progress_end] - width = coords[:bar_progress_end] - coords[:bar_start] - 2 + width = coords[:bar_progress_end] - coords[:bar_start] style = task_style(coords[:bar_start], width) output << view.content_tag(:div, ' '.html_safe, :style => style, diff --git a/test/functional/gantts_controller_test.rb b/test/functional/gantts_controller_test.rb index ddd128371..9685e44c0 100644 --- a/test/functional/gantts_controller_test.rb +++ b/test/functional/gantts_controller_test.rb @@ -252,23 +252,23 @@ class GanttsControllerTest < Redmine::ControllerTest # eCookbook assert_subject_row('project-1', row: '0', text: project.name) - assert_chart_row('div.gantt-task-todo', row: '0', style_substring: 'inset-inline-start:0px;width:138px') + assert_chart_row('div.gantt-task-todo', row: '0', style_substring: 'inset-inline-start:0px;width:140px') assert_issue_row(3, 'Bug #3', row: '1') - assert_chart_row('div.gantt-task-todo', row: '1', style_substring: 'inset-inline-start:0px;width:38px') + assert_chart_row('div.gantt-task-todo', row: '1', style_substring: 'inset-inline-start:0px;width:40px') assert_issue_row(7, 'Bug #7', row: '2') - assert_chart_row('div.gantt-task-todo', row: '2', style_substring: 'inset-inline-start:16px;width:42px') + assert_chart_row('div.gantt-task-todo', row: '2', style_substring: 'inset-inline-start:16px;width:44px') assert_issue_row(1, 'Bug #1', row: '3') - assert_chart_row('div.gantt-task-todo', row: '3', style_substring: 'inset-inline-start:52px;width:46px') + assert_chart_row('div.gantt-task-todo', row: '3', style_substring: 'inset-inline-start:52px;width:48px') # Version 1.0 assert_subject_row('version-2-project-1', row: '4', text: '1.0') - assert_chart_row('div.gantt-task-todo', row: '4', style_substring: 'inset-inline-start:48px;width:90px') + assert_chart_row('div.gantt-task-todo', row: '4', style_substring: 'inset-inline-start:48px;width:92px') assert_issue_row(2, 'Feature request #2', row: '5') - assert_chart_row('div.gantt-task-todo', row: '5', style_substring: 'inset-inline-start:48px;width:90px') + assert_chart_row('div.gantt-task-todo', row: '5', style_substring: 'inset-inline-start:48px;width:92px') # Private child of eCookbook assert_subject_row( @@ -276,16 +276,16 @@ class GanttsControllerTest < Redmine::ControllerTest row: '6', text: projects(:projects_005).name ) - assert_chart_row('div.gantt-task-todo', row: '6', style_substring: 'inset-inline-start:56px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '6', style_substring: 'inset-inline-start:56px;width:8px') assert_issue_row(6, 'Bug #6', row: '7') - assert_chart_row('div.gantt-task-todo', row: '7', style_substring: 'inset-inline-start:56px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '7', style_substring: 'inset-inline-start:56px;width:8px') assert_issue_row(9, 'Bug #9', row: '8') - assert_chart_row('div.gantt-task-todo', row: '8', style_substring: 'inset-inline-start:56px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '8', style_substring: 'inset-inline-start:56px;width:8px') assert_issue_row(10, 'Bug #10', row: '9') - assert_chart_row('div.gantt-task-todo', row: '9', style_substring: 'inset-inline-start:56px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '9', style_substring: 'inset-inline-start:56px;width:8px') assert_select 'div.gantt-task-todo[data-gantt-issue-id=?][data-gantt-relations*=?]', '10', '9' # eCookbook Subproject1 @@ -327,7 +327,7 @@ class GanttsControllerTest < Redmine::ControllerTest # eCookbook assert_subject_row('project-1', row: '0', text: projects(:projects_001).name) - assert_chart_row('div.gantt-task-todo', row: '0', style_substring: 'inset-inline-start:0px;width:258px') + assert_chart_row('div.gantt-task-todo', row: '0', style_substring: 'inset-inline-start:0px;width:260px') # Private child of eCookbook assert_subject_row( @@ -335,19 +335,19 @@ class GanttsControllerTest < Redmine::ControllerTest row: '1', text: project.name ) - assert_chart_row('div.gantt-task-todo', row: '1', style_substring: 'inset-inline-start:176px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '1', style_substring: 'inset-inline-start:176px;width:8px') # Bug #6 assert_issue_row(6, 'Bug #6', row: '2') - assert_chart_row('div.gantt-task-todo', row: '2', style_substring: 'inset-inline-start:176px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '2', style_substring: 'inset-inline-start:176px;width:8px') # Bug #9 assert_issue_row(9, 'Bug #9', row: '3') - assert_chart_row('div.gantt-task-todo', row: '3', style_substring: 'inset-inline-start:176px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '3', style_substring: 'inset-inline-start:176px;width:8px') # Bug #10 assert_issue_row(10, 'Bug #10', row: '4') - assert_chart_row('div.gantt-task-todo', row: '4', style_substring: 'inset-inline-start:176px;width:6px') + assert_chart_row('div.gantt-task-todo', row: '4', style_substring: 'inset-inline-start:176px;width:8px') assert_select 'div.gantt-task-todo[data-gantt-issue-id=?][data-gantt-relations*=?]', '10', '9' end diff --git a/test/unit/lib/redmine/helpers/gantt_test.rb b/test/unit/lib/redmine/helpers/gantt_test.rb index d01b12b1c..de97c2607 100644 --- a/test/unit/lib/redmine/helpers/gantt_test.rb +++ b/test/unit/lib/redmine/helpers/gantt_test.rb @@ -392,7 +392,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest @output_buffer = @gantt.line(start_date, gantt_start, 30, false, 'line', :format => :html, :zoom => 4) # the leftmost date (Date.today - 14 days) assert_select 'div.gantt-task-todo[style*="--gantt-task-start:0px"]', 1, @output_buffer - assert_select 'div.gantt-task-todo[style*="--gantt-task-width:2px"]', 1, @output_buffer + assert_select 'div.gantt-task-todo[style*="--gantt-task-width:4px"]', 1, @output_buffer end end @@ -402,14 +402,14 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest @output_buffer = @gantt.line(gantt_end, end_date, 30, false, 'line', :format => :html, :zoom => 4) # the rightmost date (Date.today + 14 days) assert_select 'div.gantt-task-todo[style*="--gantt-task-start:112px"]', 1, @output_buffer - assert_select 'div.gantt-task-todo[style*="--gantt-task-width:2px"]', 1, @output_buffer + assert_select 'div.gantt-task-todo[style*="--gantt-task-width:4px"]', 1, @output_buffer end end test "#line todo line should be the total width" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.gantt-task-todo[style*="--gantt-task-width:58px"]', 1 + assert_select 'div.gantt-task-todo[style*="--gantt-task-width:60px"]', 1 end test "#line late line should start from the starting point on the left" do @@ -421,21 +421,21 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest test "#line late line should be the total delayed width" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.gantt-task-late[style*="--gantt-task-width:30px"]', 1 + assert_select 'div.gantt-task-late[style*="--gantt-task-width:32px"]', 1 end test "#line late line should be the same width as task_todo if start date and end date are the same day" do create_gantt @output_buffer = @gantt.line(today - 7, today - 7, 0, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.gantt-task-late[style*="--gantt-task-width:2px"]', 1 - assert_select 'div.gantt-task-todo[style*="--gantt-task-width:2px"]', 1 + assert_select 'div.gantt-task-late[style*="--gantt-task-width:4px"]', 1 + assert_select 'div.gantt-task-todo[style*="--gantt-task-width:4px"]', 1 end test "#line late line should be the same width as task_todo if start date and today are the same day" do create_gantt @output_buffer = @gantt.line(today, today, 0, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.gantt-task-late[style*="--gantt-task-width:2px"]', 1 - assert_select 'div.gantt-task-todo[style*="--gantt-task-width:2px"]', 1 + assert_select 'div.gantt-task-late[style*="--gantt-task-width:4px"]', 1 + assert_select 'div.gantt-task-todo[style*="--gantt-task-width:4px"]', 1 end test "#line done line should start from the starting point on the left" do @@ -447,28 +447,34 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest test "#line done line should be the width for the done ratio" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, false, 'line', :format => :html, :zoom => 4) - # 15 days * 4 px * 30% - 2 px for borders = 16 px - assert_select 'div.gantt-task-done[style*="--gantt-task-width:16px"]', 1 + # 15 days * 4 px * 30% = 18 px + assert_select 'div.gantt-task-done[style*="--gantt-task-width:18px"]', 1 end test "#line done line should be the total width for 100% done ratio" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 100, false, 'line', :format => :html, :zoom => 4) - # 15 days * 4 px - 2 px for borders = 58 px - assert_select 'div.gantt-task-done[style*="--gantt-task-width:58px"]', 1 + # 15 days * 4 px = 60 px + assert_select 'div.gantt-task-done[style*="--gantt-task-width:60px"]', 1 end test "#line done line should be the total width for 100% done ratio with same start and end dates" do create_gantt @output_buffer = @gantt.line(today + 7, today + 7, 100, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.gantt-task-done[style*="--gantt-task-width:2px"]', 1 + assert_select 'div.gantt-task-done[style*="--gantt-task-width:4px"]', 1 end test "#line done line should not be the total done width if the gantt starts after start date" do create_gantt @output_buffer = @gantt.line(today - 16, today - 2, 30, false, 'line', :format => :html, :zoom => 4) assert_select 'div.gantt-task-done[style*="--gantt-task-start:0px"]', 1 - assert_select 'div.gantt-task-done[style*="--gantt-task-width:8px"]', 1 + assert_select 'div.gantt-task-done[style*="--gantt-task-width:10px"]', 1 + end + + test "#line small done ratio should not produce a negative width" do + create_gantt + @output_buffer = @gantt.line(today - 7, today - 3, 1, false, 'line', :format => :html, :zoom => 2) + assert_select 'div.gantt-task-done[style*="--gantt-task-width:0px"]', 1 end test "#line starting marker should appear at the start date" do -- 2.55.0