Display calendar in vertical list layout on mobile screens (#33682).

Patch by Takashi Kato.


git-svn-id: https://svn.redmine.org/redmine/trunk@22283 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Go MAEDA
2023-08-27 06:40:59 +00:00
parent 6653f60d74
commit 4e0bb4990c
9 changed files with 229 additions and 192 deletions

View File

@@ -33,7 +33,6 @@ class MyController < ApplicationController
helper :custom_fields helper :custom_fields
helper :queries helper :queries
helper :activities helper :activities
helper :calendars
def index def index
page page

View File

@@ -1,29 +0,0 @@
# 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

View File

@@ -1,48 +1,37 @@
<%= 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"> <ul class="cal">
<thead> <li scope="col" title="<%= l(:label_week) %>" class="calhead week-number"></li>
<tr> <% 7.times do |i| %>
<th scope="col" title="<%= l(:label_week) %>" class="week-number"></th> <li scope="col" class="calhead"><%= day_name((calendar.first_wday + i) % 7) %></li>
<% 7.times do |i| %> <% end %>
<th scope="col"><%= day_name((calendar.first_wday + i) % 7) %></th> <% 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 %>
</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>
<% end %> <% end %>
<td class="<%= calendar_day_css_classes(calendar, day) %>"> </ul>
<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>
<% end %>
<% end %>
</td>
<% if day.cwday==calendar.last_wday and day!=calendar.enddt %>
</tr><tr>
<% end %>
<% day = day + 1 %>
<% end %>
</tr>
</tbody>
</table>
<% end %> <% end %>
<%= context_menu %> <%= context_menu %>

View File

@@ -23,6 +23,8 @@ module Redmine
# 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)
@@ -47,6 +49,21 @@ module Redmine
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

View File

@@ -103,6 +103,10 @@ module Redmine
::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

View File

@@ -1100,21 +1100,60 @@ vertical-align: bottom;
} }
/***** Calendar *****/ /***** Calendar *****/
table.cal {width: 100%; margin: 0 0 6px 0; border: 1px solid #c0c0c0; border-spacing: 0; border-radius: 3px;} ul.cal {
table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; } list-style: none;
table.cal thead th.week-number {width: auto;} width: 100%;
table.cal tbody tr {height: 100px;} padding: 0;
table.cal td .icon {padding-top: 2px; padding-bottom: 3px;} display: grid;
table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em; border-bottom: 0; border-right: 0;} grid-template-columns: 2rem repeat(7, 1fr);
table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;} margin: 0;
table.cal td p.day-num {font-size: 1.1em; text-align:right;} border: 1px solid #c0c0c0;
table.cal td.odd p.day-num {color: #bbb;} border-spacing: 0;
table.cal td.today {background:#ffffdd;} border-radius: 3px;
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;} .cal .calhead {
table.cal .ending a.issue, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;} background-color:#eee;
table.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;} 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;}

View File

@@ -819,6 +819,38 @@
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) {

View File

@@ -65,19 +65,17 @@ class CalendarsControllerTest < Redmine::ControllerTest
# 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 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
assert_select( 'div.issue.hascontextmenu.tooltip.starting',
'div.issue.hascontextmenu.tooltip.starting', :text => /Add ingredients categories/
:text => /Add ingredients categories/ ) do
) do assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2'
assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' assert_select 'span.tip' do
assert_select 'span.tip' do assert_select 'img[class="gravatar"]'
assert_select 'img[class="gravatar"]'
end
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2'
end end
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2'
end end
end end
end end
@@ -89,16 +87,14 @@ class CalendarsControllerTest < Redmine::ControllerTest
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 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
assert_select( 'div.issue.hascontextmenu.tooltip.ending',
'div.issue.hascontextmenu.tooltip.ending', :text => /Cannot print recipes/
:text => /Cannot print recipes/ ) do
) do assert_select 'a.issue[href=?]', '/issues/1', :text => 'Bug #1'
assert_select 'a.issue[href=?]', '/issues/1', :text => 'Bug #1' assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '1'
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '1'
end
end end
end end
end end
@@ -120,24 +116,22 @@ class CalendarsControllerTest < Redmine::ControllerTest
) )
assert_response :success assert_response :success
assert_select 'table.cal' do assert_select 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
'div.issue.hascontextmenu.tooltip.starting.ending',
:text => /#{subject}/
) do
assert_select( assert_select(
'div.issue.hascontextmenu.tooltip.starting.ending', 'a.issue[href=?]', "/issues/#{issue.id}",
:text => /#{subject}/ :text => "Bug ##{issue.id}"
) do )
assert_select( assert_select(
'a.issue[href=?]', "/issues/#{issue.id}", 'input[name=?][type=?][value=?]',
:text => "Bug ##{issue.id}" 'ids[]',
) 'checkbox',
assert_select( issue.id.to_s
'input[name=?][type=?][value=?]', )
'ids[]',
'checkbox',
issue.id.to_s
)
end
end end
end end
end end
@@ -149,14 +143,12 @@ class CalendarsControllerTest < Redmine::ControllerTest
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 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
assert_select( 'span.icon.icon-package'
'span.icon.icon-package' ) do
) do assert_select 'a[href=?]', '/versions/2', :text => '1.0'
assert_select 'a[href=?]', '/versions/2', :text => '1.0'
end
end end
end end
end end
@@ -179,16 +171,14 @@ class CalendarsControllerTest < Redmine::ControllerTest
get :show get :show
assert_response :success assert_response :success
assert_select 'table.cal' do assert_select 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
assert_select( 'div.issue.hascontextmenu.tooltip.starting',
'div.issue.hascontextmenu.tooltip.starting', :text => /eCookbook.*Add ingredients categories/m
:text => /eCookbook.*Add ingredients categories/m ) do
) do assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2'
assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2'
assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2'
end
end end
end end
end end
@@ -200,17 +190,15 @@ class CalendarsControllerTest < Redmine::ControllerTest
get :show get :show
assert_response :success assert_response :success
assert_select 'table.cal' do assert_select 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
'span.icon.icon-package'
) do
assert_select( assert_select(
'span.icon.icon-package' 'a[href=?]', '/versions/2',
) do :text => 'eCookbook - 1.0'
assert_select( )
'a[href=?]', '/versions/2',
:text => 'eCookbook - 1.0'
)
end
end end
end end
end end
@@ -228,16 +216,16 @@ class CalendarsControllerTest < Redmine::ControllerTest
assert_response :success assert_response :success
end end
assert_select 'tr' do assert_select 'ul' do
assert_select 'td.week-number', :text => '53' assert_select 'li.week-number:nth-of-type(2)', :text => /53$/
assert_select 'td.odd', :text => '27' assert_select 'li.odd', :text => /^27/
assert_select 'td.even', :text => '2' assert_select 'li.even', :text => /^2/
end end
assert_select 'tr' do assert_select 'ul' do
assert_select 'td.week-number', :text => '1' assert_select 'li.week-number', :text => /1$/
assert_select 'td.odd', :text => '3' assert_select 'li.odd', :text => /^3/
assert_select 'td.even', :text => '9' assert_select 'li.even', :text => /^9/
end end
with_settings :start_of_week => 1 do with_settings :start_of_week => 1 do
@@ -251,16 +239,16 @@ class CalendarsControllerTest < Redmine::ControllerTest
assert_response :success assert_response :success
end end
assert_select 'tr' do assert_select 'ul' do
assert_select 'td.week-number', :text => '53' assert_select 'li.week-number:nth-of-type(2)', :text => /53$/
assert_select 'td.even', :text => '28' assert_select 'li.even', :text => /^28/
assert_select 'td.even', :text => '3' assert_select 'li.even', :text => /^3/
end end
assert_select 'tr' do assert_select 'ul' do
assert_select 'td.week-number', :text => '1' assert_select 'li.week-number', :text => /1$/
assert_select 'td.even', :text => '4' assert_select 'li.even', :text => /^4/
assert_select 'td.even', :text => '10' assert_select 'li.even', :text => /^10/
end end
end end
@@ -296,12 +284,12 @@ class CalendarsControllerTest < Redmine::ControllerTest
) )
assert_response :success assert_response :success
assert_select 'tr:nth-child(2)' do assert_select 'ul' do
assert_select 'td.week-number', :text => '49' 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 'li.nwday', 10
assert_select 'td.nwday', :text => '4' assert_select 'li.nwday', :text => /^4/
assert_select 'td.nwday', :text => '10' assert_select 'li.nwday', :text => /^10/
end end
end end
end end

View File

@@ -444,24 +444,22 @@ class MyControllerTest < Redmine::ControllerTest
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 'ul.cal' do
assert_select 'tr' do assert_select 'li' do
assert_select 'td' do assert_select(
'div.issue.hascontextmenu.tooltip.starting.ending',
:text => /eCookbook.*#{subject}/m
) do
assert_select( assert_select(
'div.issue.hascontextmenu.tooltip.starting.ending', 'a.issue[href=?]', "/issues/#{issue.id}",
:text => /eCookbook.*#{subject}/m :text => "Bug ##{issue.id}"
) do )
assert_select( assert_select(
'a.issue[href=?]', "/issues/#{issue.id}", 'input[name=?][type=?][value=?]',
:text => "Bug ##{issue.id}" 'ids[]',
) 'checkbox',
assert_select( issue.id.to_s
'input[name=?][type=?][value=?]', )
'ids[]',
'checkbox',
issue.id.to_s
)
end
end end
end end
end end