Patch by Takashi Kato. git-svn-id: https://svn.redmine.org/redmine/trunk@22283 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/5.1.0
helper :custom_fields | helper :custom_fields | ||||
helper :queries | helper :queries | ||||
helper :activities | helper :activities | ||||
helper :calendars | |||||
def index | def index | ||||
page | page |
# frozen_string_literal: true | |||||
# Redmine - project management software | |||||
# Copyright (C) 2006-2023 Jean-Philippe Lang | |||||
# | |||||
# This program is free software; you can redistribute it and/or | |||||
# modify it under the terms of the GNU General Public License | |||||
# as published by the Free Software Foundation; either version 2 | |||||
# of the License, or (at your option) any later version. | |||||
# | |||||
# This program is distributed in the hope that it will be useful, | |||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
# GNU General Public License for more details. | |||||
# | |||||
# You should have received a copy of the GNU General Public License | |||||
# along with this program; if not, write to the Free Software | |||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
module CalendarsHelper | |||||
include Redmine::Utils::DateCalculation | |||||
def calendar_day_css_classes(calendar, day) | |||||
css = day.month==calendar.month ? +'even' : +'odd' | |||||
css << " today" if User.current.today == day | |||||
css << " nwday" if non_working_week_days.include?(day.cwday) | |||||
css | |||||
end | |||||
end |
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%> | <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%> | ||||
<%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %> | <%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %> | ||||
<table class="cal"> | |||||
<thead> | |||||
<tr> | |||||
<th scope="col" title="<%= l(:label_week) %>" class="week-number"></th> | |||||
<% 7.times do |i| %> | |||||
<th scope="col"><%= day_name((calendar.first_wday + i) % 7) %></th> | |||||
<% end %> | |||||
</tr> | |||||
</thead> | |||||
<tbody> | |||||
<tr> | |||||
<% day = calendar.startdt %> | |||||
<% while day <= calendar.enddt %> | |||||
<% if day.cwday == calendar.first_wday %> | |||||
<td class='week-number' title='<%= l(:label_week) %>'> | |||||
<%= (day + (11 - day.cwday) % 7).cweek %> | |||||
</td> | |||||
<ul class="cal"> | |||||
<li scope="col" title="<%= l(:label_week) %>" class="calhead week-number"></li> | |||||
<% 7.times do |i| %> | |||||
<li scope="col" class="calhead"><%= day_name((calendar.first_wday + i) % 7) %></li> | |||||
<% end %> | <% end %> | ||||
<td class="<%= calendar_day_css_classes(calendar, day) %>"> | |||||
<p class="day-num"><%= day.day %></p> | |||||
<% calendar.events_on(day).each do |i| %> | |||||
<% if i.is_a? Issue %> | |||||
<div class="<%= i.css_classes %> <%= 'starting' if day == i.start_date %> <%= 'ending' if day == i.due_date %> tooltip hascontextmenu"> | |||||
<%= "#{i.project} -" unless @project && @project == i.project %> | |||||
<%= link_to_issue i, :truncate => 30 %> | |||||
<span class="tip"><%= render_issue_tooltip i %></span> | |||||
<%= check_box_tag 'ids[]', i.id, false, :style => 'display:none;', :id => nil %> | |||||
</div> | |||||
<% else %> | |||||
<span class="icon icon-package"> | |||||
<%= "#{i.project} -" unless @project && @project == i.project %> | |||||
<%= link_to_version i %> | |||||
</span> | |||||
<% calendar.format_month.each_slice(7) do |week| %> | |||||
<li class='week-number'> | |||||
<span class="label-week"><%= l(:label_week) %></span> <%= calendar.week_number week.first %> | |||||
</li> | |||||
<% week.each do |day| %> | |||||
<li class="<%= calendar.day_css_classes day %> calbody"> | |||||
<p class="day-num"><%= day.day %> | |||||
<span class="abbr-day">(<%= abbr_day_name(day.cwday) %>)</span> | |||||
</p> | |||||
<% calendar.events_on(day).each do |i| %> | |||||
<% if i.is_a? Issue %> | |||||
<%= tag.div class: [ i.css_classes, 'tooltip hascontextmenu', starting: day == i.start_date, ending: day == i.due_date] do %> | |||||
<%= "#{i.project} -" unless @project && @project == i.project %> | |||||
<%= link_to_issue i, :truncate => 30 %> | |||||
<span class="tip"><%= render_issue_tooltip i %></span> | |||||
<%= check_box_tag 'ids[]', i.id, false, :style => 'display:none;', :id => nil %> | |||||
<% end %> | |||||
<% else %> | |||||
<span class="icon icon-package"> | |||||
<%= "#{i.project} -" unless @project && @project == i.project %> | |||||
<%= link_to_version i %> | |||||
</span> | |||||
<% end %> | |||||
<% end %> | |||||
</li> | |||||
<% end %> | |||||
<% end %> | <% end %> | ||||
<% end %> | |||||
</td> | |||||
<% if day.cwday==calendar.last_wday and day!=calendar.enddt %> | |||||
</tr><tr> | |||||
<% end %> | |||||
<% day = day + 1 %> | |||||
<% end %> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</ul> | |||||
<% end %> | <% end %> | ||||
<%= context_menu %> | <%= context_menu %> |
# Simple class to compute the start and end dates of a calendar | # Simple class to compute the start and end dates of a calendar | ||||
class Calendar | class Calendar | ||||
include Redmine::I18n | include Redmine::I18n | ||||
include Redmine::Utils::DateCalculation | |||||
attr_reader :startdt, :enddt | attr_reader :startdt, :enddt | ||||
def initialize(date, lang = current_language, period = :month) | def initialize(date, lang = current_language, period = :month) | ||||
end | end | ||||
end | end | ||||
def format_month | |||||
(@startdt..@enddt).to_a | |||||
end | |||||
def week_number(day) | |||||
(day + (11 - day.cwday) % 7).cweek | |||||
end | |||||
def day_css_classes(day) | |||||
css = day.month==month ? +'even' : +'odd' | |||||
css << " today" if User.current.today == day | |||||
css << " nwday" if non_working_week_days.include?(day.cwday) | |||||
css | |||||
end | |||||
# Sets calendar events | # Sets calendar events | ||||
def events=(events) | def events=(events) | ||||
@events = events | @events = events |
::I18n.t('date.day_names')[day % 7] | ::I18n.t('date.day_names')[day % 7] | ||||
end | end | ||||
def abbr_day_name(day) | |||||
::I18n.t('date.abbr_day_names')[day % 7] | |||||
end | |||||
def day_letter(day) | def day_letter(day) | ||||
::I18n.t('date.abbr_day_names')[day % 7].first | ::I18n.t('date.abbr_day_names')[day % 7].first | ||||
end | end |
} | } | ||||
/***** Calendar *****/ | /***** Calendar *****/ | ||||
table.cal {width: 100%; margin: 0 0 6px 0; border: 1px solid #c0c0c0; border-spacing: 0; border-radius: 3px;} | |||||
table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; } | |||||
table.cal thead th.week-number {width: auto;} | |||||
table.cal tbody tr {height: 100px;} | |||||
table.cal td .icon {padding-top: 2px; padding-bottom: 3px;} | |||||
table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em; border-bottom: 0; border-right: 0;} | |||||
table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;} | |||||
table.cal td p.day-num {font-size: 1.1em; text-align:right;} | |||||
table.cal td.odd p.day-num {color: #bbb;} | |||||
table.cal td.today {background:#ffffdd;} | |||||
table.cal td.today p.day-num {font-weight: bold;} | |||||
table.cal td.nwday:not(.odd) {background-color:#f1f1f1;} | |||||
table.cal .starting a.issue, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;} | |||||
table.cal .ending a.issue, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;} | |||||
table.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;} | |||||
ul.cal { | |||||
list-style: none; | |||||
width: 100%; | |||||
padding: 0; | |||||
display: grid; | |||||
grid-template-columns: 2rem repeat(7, 1fr); | |||||
margin: 0; | |||||
border: 1px solid #c0c0c0; | |||||
border-spacing: 0; | |||||
border-radius: 3px; | |||||
} | |||||
.cal .calhead { | |||||
background-color:#eee; | |||||
text-align: center; | |||||
font-weight: bold; | |||||
padding: 4px | |||||
} | |||||
.cal .week-number { | |||||
background-color:#eee; | |||||
border:none; | |||||
font-size: 1em; | |||||
padding: 4px; | |||||
text-align: center; | |||||
} | |||||
.cal .week-number .label-week { | |||||
display: none; | |||||
} | |||||
.cal .calbody { | |||||
border: 1px solid #d7d7d7; | |||||
vertical-align: top; | |||||
font-size: 0.9em; | |||||
border-bottom: 0; | |||||
border-right: 0; | |||||
line-height: 1.2; | |||||
min-height: calc(1.2em * 6); | |||||
padding: 2px; | |||||
} | |||||
.cal .calbody p.day-num {font-size: 1.1em; text-align:right;} | |||||
.cal .calbody .abbr-day {display:none} | |||||
.cal .calbody.odd p.day-num {color: #bbb;} | |||||
.cal .calbody.today {background:#ffd;} | |||||
.cal .calbody.today p.day-num {font-weight: bold;} | |||||
.cal .calbody .icon {padding-top: 2px; padding-bottom: 3px;} | |||||
.cal .calbody.nwday:not(.odd) {background-color:#f1f1f1;} | |||||
.cal .starting a.issue, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;} | |||||
.cal .ending a.issue, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;} | |||||
.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;} | |||||
p.cal.legend span {display:block;} | p.cal.legend span {display:block;} | ||||
.controller-calendars p.buttons {margin-top: unset;} | .controller-calendars p.buttons {margin-top: unset;} | ||||
margin-left: 0; | margin-left: 0; | ||||
width: 100%; | width: 100%; | ||||
} | } | ||||
/* Calendar */ | |||||
ul.cal { | |||||
display: block | |||||
} | |||||
.cal .calhead { | |||||
display: none | |||||
} | |||||
.cal .calbody { | |||||
min-height: calc(1.2em * 3); | |||||
} | |||||
.cal .calbody .abbr-day { | |||||
display: inline; | |||||
} | |||||
.cal .week-number { | |||||
border: 1px solid #c0c0c0; | |||||
text-align: left; | |||||
font-weight: bold; | |||||
background-color: #def; | |||||
} | |||||
.cal .week-number .label-week { | |||||
display: inline; | |||||
} | |||||
.cal .calbody p.day-num { | |||||
font-size: 1.1em; | |||||
text-align: left; | |||||
} | |||||
} | } | ||||
@media all and (max-width: 599px) { | @media all and (max-width: 599px) { |
# Assert context menu on issues | # Assert context menu on issues | ||||
assert_select 'form[data-cm-url=?]', '/issues/context_menu' | assert_select 'form[data-cm-url=?]', '/issues/context_menu' | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.starting', | |||||
:text => /Add ingredients categories/ | |||||
) do | |||||
assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' | |||||
assert_select 'span.tip' do | |||||
assert_select 'img[class="gravatar"]' | |||||
end | |||||
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2' | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.starting', | |||||
:text => /Add ingredients categories/ | |||||
) do | |||||
assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' | |||||
assert_select 'span.tip' do | |||||
assert_select 'img[class="gravatar"]' | |||||
end | end | ||||
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2' | |||||
end | end | ||||
end | end | ||||
end | end | ||||
get(:show, :params => {:project_id => 1}) | get(:show, :params => {:project_id => 1}) | ||||
assert_response :success | assert_response :success | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.ending', | |||||
:text => /Cannot print recipes/ | |||||
) do | |||||
assert_select 'a.issue[href=?]', '/issues/1', :text => 'Bug #1' | |||||
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '1' | |||||
end | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.ending', | |||||
:text => /Cannot print recipes/ | |||||
) do | |||||
assert_select 'a.issue[href=?]', '/issues/1', :text => 'Bug #1' | |||||
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '1' | |||||
end | end | ||||
end | end | ||||
end | end | ||||
) | ) | ||||
assert_response :success | assert_response :success | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.starting.ending', | |||||
:text => /#{subject}/ | |||||
) do | |||||
assert_select( | assert_select( | ||||
'div.issue.hascontextmenu.tooltip.starting.ending', | |||||
:text => /#{subject}/ | |||||
) do | |||||
assert_select( | |||||
'a.issue[href=?]', "/issues/#{issue.id}", | |||||
:text => "Bug ##{issue.id}" | |||||
) | |||||
assert_select( | |||||
'input[name=?][type=?][value=?]', | |||||
'ids[]', | |||||
'checkbox', | |||||
issue.id.to_s | |||||
) | |||||
end | |||||
'a.issue[href=?]', "/issues/#{issue.id}", | |||||
:text => "Bug ##{issue.id}" | |||||
) | |||||
assert_select( | |||||
'input[name=?][type=?][value=?]', | |||||
'ids[]', | |||||
'checkbox', | |||||
issue.id.to_s | |||||
) | |||||
end | end | ||||
end | end | ||||
end | end | ||||
get(:show, :params => {:project_id => 1}) | get(:show, :params => {:project_id => 1}) | ||||
assert_response :success | assert_response :success | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select( | |||||
'span.icon.icon-package' | |||||
) do | |||||
assert_select 'a[href=?]', '/versions/2', :text => '1.0' | |||||
end | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'span.icon.icon-package' | |||||
) do | |||||
assert_select 'a[href=?]', '/versions/2', :text => '1.0' | |||||
end | end | ||||
end | end | ||||
end | end | ||||
get :show | get :show | ||||
assert_response :success | assert_response :success | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.starting', | |||||
:text => /eCookbook.*Add ingredients categories/m | |||||
) do | |||||
assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' | |||||
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2' | |||||
end | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.starting', | |||||
:text => /eCookbook.*Add ingredients categories/m | |||||
) do | |||||
assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' | |||||
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2' | |||||
end | end | ||||
end | end | ||||
end | end | ||||
get :show | get :show | ||||
assert_response :success | assert_response :success | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'span.icon.icon-package' | |||||
) do | |||||
assert_select( | assert_select( | ||||
'span.icon.icon-package' | |||||
) do | |||||
assert_select( | |||||
'a[href=?]', '/versions/2', | |||||
:text => 'eCookbook - 1.0' | |||||
) | |||||
end | |||||
'a[href=?]', '/versions/2', | |||||
:text => 'eCookbook - 1.0' | |||||
) | |||||
end | end | ||||
end | end | ||||
end | end | ||||
assert_response :success | assert_response :success | ||||
end | end | ||||
assert_select 'tr' do | |||||
assert_select 'td.week-number', :text => '53' | |||||
assert_select 'td.odd', :text => '27' | |||||
assert_select 'td.even', :text => '2' | |||||
assert_select 'ul' do | |||||
assert_select 'li.week-number:nth-of-type(2)', :text => /53$/ | |||||
assert_select 'li.odd', :text => /^27/ | |||||
assert_select 'li.even', :text => /^2/ | |||||
end | end | ||||
assert_select 'tr' do | |||||
assert_select 'td.week-number', :text => '1' | |||||
assert_select 'td.odd', :text => '3' | |||||
assert_select 'td.even', :text => '9' | |||||
assert_select 'ul' do | |||||
assert_select 'li.week-number', :text => /1$/ | |||||
assert_select 'li.odd', :text => /^3/ | |||||
assert_select 'li.even', :text => /^9/ | |||||
end | end | ||||
with_settings :start_of_week => 1 do | with_settings :start_of_week => 1 do | ||||
assert_response :success | assert_response :success | ||||
end | end | ||||
assert_select 'tr' do | |||||
assert_select 'td.week-number', :text => '53' | |||||
assert_select 'td.even', :text => '28' | |||||
assert_select 'td.even', :text => '3' | |||||
assert_select 'ul' do | |||||
assert_select 'li.week-number:nth-of-type(2)', :text => /53$/ | |||||
assert_select 'li.even', :text => /^28/ | |||||
assert_select 'li.even', :text => /^3/ | |||||
end | end | ||||
assert_select 'tr' do | |||||
assert_select 'td.week-number', :text => '1' | |||||
assert_select 'td.even', :text => '4' | |||||
assert_select 'td.even', :text => '10' | |||||
assert_select 'ul' do | |||||
assert_select 'li.week-number', :text => /1$/ | |||||
assert_select 'li.even', :text => /^4/ | |||||
assert_select 'li.even', :text => /^10/ | |||||
end | end | ||||
end | end | ||||
) | ) | ||||
assert_response :success | assert_response :success | ||||
assert_select 'tr:nth-child(2)' do | |||||
assert_select 'td.week-number', :text => '49' | |||||
assert_select 'ul' do | |||||
assert_select 'li.week-number:nth-of-type(2)', :text => /48$/ | |||||
# non working days should have "nwday" CSS class | # non working days should have "nwday" CSS class | ||||
assert_select 'td.nwday', 2 | |||||
assert_select 'td.nwday', :text => '4' | |||||
assert_select 'td.nwday', :text => '10' | |||||
assert_select 'li.nwday', 10 | |||||
assert_select 'li.nwday', :text => /^4/ | |||||
assert_select 'li.nwday', :text => /^10/ | |||||
end | end | ||||
end | end | ||||
end | end |
assert_select 'form[data-cm-url=?]', '/issues/context_menu' | assert_select 'form[data-cm-url=?]', '/issues/context_menu' | ||||
assert_select 'table.cal' do | |||||
assert_select 'tr' do | |||||
assert_select 'td' do | |||||
assert_select 'ul.cal' do | |||||
assert_select 'li' do | |||||
assert_select( | |||||
'div.issue.hascontextmenu.tooltip.starting.ending', | |||||
:text => /eCookbook.*#{subject}/m | |||||
) do | |||||
assert_select( | |||||
'a.issue[href=?]', "/issues/#{issue.id}", | |||||
:text => "Bug ##{issue.id}" | |||||
) | |||||
assert_select( | assert_select( | ||||
'div.issue.hascontextmenu.tooltip.starting.ending', | |||||
:text => /eCookbook.*#{subject}/m | |||||
) do | |||||
assert_select( | |||||
'a.issue[href=?]', "/issues/#{issue.id}", | |||||
:text => "Bug ##{issue.id}" | |||||
) | |||||
assert_select( | |||||
'input[name=?][type=?][value=?]', | |||||
'ids[]', | |||||
'checkbox', | |||||
issue.id.to_s | |||||
) | |||||
end | |||||
'input[name=?][type=?][value=?]', | |||||
'ids[]', | |||||
'checkbox', | |||||
issue.id.to_s | |||||
) | |||||
end | end | ||||
end | end | ||||
end | end |