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>
<th scope="col" title="<%= l(:label_week) %>" class="week-number"></th>
<% 7.times do |i| %> <% 7.times do |i| %>
<th scope="col"><%= day_name((calendar.first_wday + i) % 7) %></th> <li scope="col" class="calhead"><%= day_name((calendar.first_wday + i) % 7) %></li>
<% end %> <% end %>
</tr> <% calendar.format_month.each_slice(7) do |week| %>
</thead> <li class='week-number'>
<tbody> <span class="label-week"><%= l(:label_week) %></span> <%= calendar.week_number week.first %>
<tr> </li>
<% day = calendar.startdt %> <% week.each do |day| %>
<% while day <= calendar.enddt %> <li class="<%= calendar.day_css_classes day %> calbody">
<% if day.cwday == calendar.first_wday %> <p class="day-num"><%= day.day %>
<td class='week-number' title='<%= l(:label_week) %>'> <span class="abbr-day">(<%= abbr_day_name(day.cwday) %>)</span>
<%= (day + (11 - day.cwday) % 7).cweek %> </p>
</td> <% calendar.events_on(day).each do |i| %>
<% 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 %> <% if i.is_a? Issue %>
<div class="<%= i.css_classes %> <%= 'starting' if day == i.start_date %> <%= 'ending' if day == i.due_date %> tooltip hascontextmenu"> <%= 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 %> <%= "#{i.project} -" unless @project && @project == i.project %>
<%= link_to_issue i, :truncate => 30 %> <%= link_to_issue i, :truncate => 30 %>
<span class="tip"><%= render_issue_tooltip i %></span> <span class="tip"><%= render_issue_tooltip i %></span>
<%= check_box_tag 'ids[]', i.id, false, :style => 'display:none;', :id => nil %> <%= check_box_tag 'ids[]', i.id, false, :style => 'display:none;', :id => nil %>
</div> <% end %>
<% else %> <% else %>
<span class="icon icon-package"> <span class="icon icon-package">
<%= "#{i.project} -" unless @project && @project == i.project %> <%= "#{i.project} -" unless @project && @project == i.project %>
<%= link_to_version i %> <%= link_to_version i %>
</span> </span>
<% end %> <% end %>
<% end %> <% end %>
</td> </li>
<% if day.cwday==calendar.last_wday and day!=calendar.enddt %> <% end %>
</tr><tr> <% end %>
<% end %> </ul>
<% 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,9 +65,8 @@ 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/
@@ -81,7 +80,6 @@ class CalendarsControllerTest < Redmine::ControllerTest
end end
end end
end end
end
def test_show_issue_due_date def test_show_issue_due_date
travel_to issues(:issues_001).due_date travel_to issues(:issues_001).due_date
@@ -89,9 +87,8 @@ 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/
@@ -102,7 +99,6 @@ class CalendarsControllerTest < Redmine::ControllerTest
end end
end end
end end
end
test "show issue of start and due dates are same" do test "show issue of start and due dates are same" do
subject = 'start and due dates are same' subject = 'start and due dates are same'
@@ -120,9 +116,8 @@ 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( assert_select(
'div.issue.hascontextmenu.tooltip.starting.ending', 'div.issue.hascontextmenu.tooltip.starting.ending',
:text => /#{subject}/ :text => /#{subject}/
@@ -141,7 +136,6 @@ class CalendarsControllerTest < Redmine::ControllerTest
end end
end end
end end
end
def test_show_version def test_show_version
travel_to versions(:versions_002).effective_date travel_to versions(:versions_002).effective_date
@@ -149,9 +143,8 @@ 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
@@ -160,7 +153,6 @@ class CalendarsControllerTest < Redmine::ControllerTest
end end
end end
end end
end
def test_show_should_run_custom_queries def test_show_should_run_custom_queries
@query = IssueQuery.create!(:name => 'Calendar Query', :visibility => IssueQuery::VISIBILITY_PUBLIC) @query = IssueQuery.create!(:name => 'Calendar Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
@@ -179,9 +171,8 @@ 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
@@ -192,7 +183,6 @@ class CalendarsControllerTest < Redmine::ControllerTest
end end
end end
end end
end
def test_cross_project_calendar_version def test_cross_project_calendar_version
travel_to versions(:versions_002).effective_date travel_to versions(:versions_002).effective_date
@@ -200,9 +190,8 @@ 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(
'span.icon.icon-package' 'span.icon.icon-package'
) do ) do
@@ -214,7 +203,6 @@ class CalendarsControllerTest < Redmine::ControllerTest
end end
end end
end end
end
def test_week_number_calculation def test_week_number_calculation
with_settings :start_of_week => 7 do with_settings :start_of_week => 7 do
@@ -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,9 +444,8 @@ 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( assert_select(
'div.issue.hascontextmenu.tooltip.starting.ending', 'div.issue.hascontextmenu.tooltip.starting.ending',
:text => /eCookbook.*#{subject}/m :text => /eCookbook.*#{subject}/m
@@ -465,7 +464,6 @@ class MyControllerTest < Redmine::ControllerTest
end end
end end
end end
end
def test_update_account def test_update_account
put( put(