[Bps-public-commit] rtx-calendar branch multiple-days-events created. 1.05-23-g4a28019
BPS Git Server
git at git.bestpractical.com
Mon Sep 11 20:23:08 UTC 2023
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rtx-calendar".
The branch, multiple-days-events has been created
at 4a280196db87d18d53049b321c0d34ffe4f23790 (commit)
- Log -----------------------------------------------------------------
commit 4a280196db87d18d53049b321c0d34ffe4f23790
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Mon Sep 11 15:56:40 2023 -0300
Make MyCalendar Multiday events compatible
Add the multiday events to the MyCalendar portlet.
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index f95cc9d..fb2e227 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -14,21 +14,67 @@
</thead>
<tbody>
<tr>
-% $date = $begin->clone;
-% while ($date <= $end) {
-% my @classes = ();
-% push @classes, "today" if (DateTime->compare($today, $date) == 0);
-% push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+<%perl>
+my %week_ticket_position;
+my $day_of_week = 1;
+$date = $begin->clone;
+
+while ($date <= $end) {
+ my @classes = ();
+ push @classes, "today" if (DateTime->compare($today, $date) == 0);
+ push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+ for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
+ # check if ticket was already displayed this week, if not, we need to find a
+ # position for it
+ unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
+ # new tickets should assume the first empty spot.
+ my $i = 1;
+ my $free_index = 0;
+ for my $index ( sort keys %week_ticket_position ) {
+ if ( $week_ticket_position{$index}{id} eq "" ) {
+ $free_index = $i;
+ last;
+ }
+ $i++;
+ }
+ # if we found a free spot, we place the ticket there
+ if ( $free_index != 0 ) {
+ $week_ticket_position{$free_index}{id} = $t->id;
+ $week_ticket_position{$free_index}{TicketObj} = $t;
+ }
+ # if not, we add it to the end of the array
+ else {
+ $week_ticket_position{((scalar keys %week_ticket_position)+1)}{id} = $t->id;
+ $week_ticket_position{((scalar keys %week_ticket_position))}{TicketObj} = $t;
+ }
+ }
+ }
+</%perl>
<td class="<% @classes %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
-
-% for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
- <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+% for my $index ( sort keys %week_ticket_position ) {
+% if ( grep { $_->id eq $week_ticket_position{$index}{id} }
+% @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
+% my $t = $week_ticket_position{$index}{TicketObj};
+ <& /Elements/CalendarEvent,
+ Object => $t,
+ Date => $date,
+ DateTypes => \%DateTypes,
+ DayOfWeek => $day_of_week,
+ TicketsSpanningDays => $TicketsSpanningDays,
+ WeekTicketPosition => \%week_ticket_position,
+ CurrentPostion => $index,
+ &>
+% }
+% else {
+% # if there's no ticket for this position, we add an empty space
+ <div class="day"> </div>
+% }
% }
-
</div></td>
% $date = $set->next($date);
+% $day_of_week = $day_of_week + 1;
% }
</tr>
</tbody>
@@ -95,8 +141,7 @@ $Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->str
$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$Query, FormatRef => \$Format );
-my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
-
</%INIT>
commit 5e553d7fbe2d58438691a85badf8d2d62a4eb9f6
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Mon Sep 11 10:50:17 2023 -0300
Create Element for the Calendar footer
Move the footer with help information of the calendar to an Element
file to make it easier to maintain.
diff --git a/html/Elements/CalendarFooter b/html/Elements/CalendarFooter
new file mode 100644
index 0000000..86b7ccb
--- /dev/null
+++ b/html/Elements/CalendarFooter
@@ -0,0 +1,38 @@
+<&| /Widgets/TitleBox, title => loc('Help') &>
+
+<h4 class="mt-2"><&|/l&>Displaying reminders</&>:</h4>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Edit.html">} . loc("Advanced") . '</a>' &>
+If you want to see reminders on the calendar, you need to go to the [_1] tab
+of your query and explicitly add the following clause to it:
+</&>
+ <pre>
+ AND ( Type = 'ticket' OR Type = 'reminder' )
+</pre>
+</p>
+
+<h4><&|/l&>Displaying other kind of dates</&>:</h4>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
+By default, RTx::Calendar displays Due and Starts dates. You can select other
+date fields with the Display Columns section in the [_1].
+The following format will display the two additional date fields, LastUpdated and a
+custom field called Maintenance Date:
+</&>
+<pre>
+ '<small>__Due__</small>',
+ '<small>__Starts__</small>',
+ '<small>__LastUpdated__</small>',
+ '<small>__CustomField.{Maintenance Date}__</small>'
+</pre>
+</p>
+
+<h4><&|/l&>Changing the default query</&>:</h4>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
+You can change the default query used by Calendar.html and the MyCalendar
+portlet by saving a query with the name <code>calendar</code> in the [_1].
+</&>
+</p>
+
+</&>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 5b49c6a..01ef818 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -200,45 +200,7 @@ while ($date <= $end) {
</table>
</&>
-
-<&| /Widgets/TitleBox, title => loc('Help') &>
-
-<h4 class="mt-2"><&|/l&>Displaying reminders</&>:</h4>
-<p>
-<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Edit.html">} . loc("Advanced") . '</a>' &>
-If you want to see reminders on the calendar, you need to go to the [_1] tab
-of your query and explicitly add the following clause to it:
-</&>
- <pre>
- AND ( Type = 'ticket' OR Type = 'reminder' )
-</pre>
-</p>
-
-<h4><&|/l&>Displaying other kind of dates</&>:</h4>
-<p>
-<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
-By default, RTx::Calendar displays Due and Starts dates. You can select other
-date fields with the Display Columns section in the [_1].
-The following format will display the two additional date fields, LastUpdated and a
-custom field called Maintenance Date:
-</&>
-<pre>
- '<small>__Due__</small>',
- '<small>__Starts__</small>',
- '<small>__LastUpdated__</small>',
- '<small>__CustomField.{Maintenance Date}__</small>'
-</pre>
-</p>
-
-<h4><&|/l&>Changing the default query</&>:</h4>
-<p>
-<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
-You can change the default query used by Calendar.html and the MyCalendar
-portlet by saving a query with the name <code>calendar</code> in the [_1].
-</&>
-</p>
-
-</&>
+ <& /Elements/CalendarFooter &>
</div>
<%INIT>
if ($FilterOnStatusClear) {
commit 385130dd1b6e5b0fe9c49039d8d0df5fb3af2747
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Mon Sep 11 10:28:11 2023 -0300
Create Element for the Sidebar
Move the sidebar of the calendar to an Element file, making it easier to
maintain and understand the code.
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
new file mode 100644
index 0000000..c752ae8
--- /dev/null
+++ b/html/Elements/CalendarSidebar
@@ -0,0 +1,108 @@
+<%args>
+$BaseQuery => undef
+$Month => undef
+$Year => undef
+$Format => undef
+$Order => undef
+$OrderBy => undef
+$RowsPerPage => undef
+ at FilterOnStatus => undef
+ at Dates => undef
+</%args>
+<div class="calendar-sidebar-toggle-content">
+ <a title="Toggle Filter" href="javascript:;" class="calendar-toggle-sidebar"></a>
+ <div class="calendar-sidebar">
+ <&| /Widgets/TitleBox,
+ title => loc('Filter on Status'),
+ class => 'calendar-filter-status-box',
+ &>
+
+ <form id="FilterOnStatusForm"
+ action="<%$RT::WebPath%>/Search/Calendar.html" method="post">
+ <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
+ <input type="hidden" name="Month" value="<%$Month%>" />
+ <input type="hidden" name="Year" value="<%$Year%>" />
+ <input type="hidden" name="Format" value="<%$Format%>" />
+ <input type="hidden" name="Order" value="<%$Order%>" />
+ <input type="hidden" name="OrderBy" value="<%$OrderBy%>" />
+ <input type="hidden" name="RowsPerPage" value="<%$RowsPerPage%>" />
+ <select name="FilterOnStatus" id="FilterOnStatus"
+ class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
+% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
+ <option value="<% $Status %>"
+% if (@FilterOnStatus && $FilterOnStatus[0]) {
+ <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
+% }
+ ><% loc($Status) %></option>
+% }
+ </select>
+ <div class="text-center">
+ <input type="submit" value="<% loc('Filter') %>" class="mr-2 button btn btn-primary" />
+ <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
+ value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
+ </div>
+ </form>
+ </&>
+
+ <&| /Widgets/TitleBox,
+ title => loc('Event Types'),
+ &>
+% foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
+ <span class="tip">
+ <span class="tipimg">
+ <img
+ src="<% $RT::WebImagesURL %>/<%
+ $CalendarIcons{$CalendarIconsTranslated{$TranslatedLegend}}|n %>" />
+ </span>
+ <span class="tiplegend">
+ <% $TranslatedLegend %>
+ </span>
+ </span><br />
+% }
+ </&>
+
+<&| /Widgets/TitleBox,
+ title => loc('State Colors'),
+ &>
+% my %ColorStatusMap = RT->Config->Get('CalendarStatusColorMap');
+% foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
+ <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
+% }
+</&>
+
+ </div>
+</div>
+
+<script type="text/javascript">
+ jQuery(document).ready(function() {
+ jQuery('.calendar-toggle-sidebar').click(function() {
+ jQuery('.calendar-sidebar').toggle();
+ jQuery('.calendar-sidebar-toggle-content,.calendar-toggle-sidebar').toggleClass('sidebar-off');
+ jQuery('.calendar-content').toggleClass('sidebar-off');
+ });
+ });
+</script>
+
+<%init>
+my %CalendarIcons = RT->Config->Get('CalendarIcons');
+# Sort the legend after translation
+my %CalendarIconsTranslated;
+my $LegendLabel;
+LEGEND:
+foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
+ # We might have multiple labels for the same icon
+ # such as "LastUpdated, CF.{Maintenance Date}"
+ # so we need to split them and translate them individually
+ my @LegendLabels = split ',', $legend;
+ $LegendLabel = join ', ',
+ map {
+ my $label = $_;
+ next LEGEND unless ( grep { $label eq $_ } @Dates );
+ $_ =~ s/^\s+|\s+$//g;
+ $_ =~ s/^CF\.\{(.*)\}/$1/;
+ $_ = 'Last Updated' if $_ eq 'LastUpdated';
+ loc($_)
+ } @LegendLabels;
+ $CalendarIconsTranslated{$LegendLabel} = $legend;
+}
+</%init>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index fd2926c..5b49c6a 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -22,93 +22,17 @@ $FilterOnStatusClear => undef
<& /Elements/Tabs &>
% }
-<div class="calendar-sidebar-toggle-content">
-<a title="Toggle Filter" href="javascript:;" class="calendar-toggle-sidebar"></a>
-<div class="calendar-sidebar">
-<&| /Widgets/TitleBox,
- title => loc('Filter on Status'),
- class => 'calendar-filter-status-box',
- &>
-
- <form id="FilterOnStatusForm"
- action="<%$RT::WebPath%>/Search/Calendar.html" method="post">
- <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
- <input type="hidden" name="Month" value="<%$Month%>" />
- <input type="hidden" name="Year" value="<%$Year%>" />
- <input type="hidden" name="Format" value="<%$Format%>" />
- <input type="hidden" name="Order" value="<%$Order%>" />
- <input type="hidden" name="OrderBy" value="<%$OrderBy%>" />
- <input type="hidden" name="RowsPerPage" value="<%$RowsPerPage%>" />
- <select name="FilterOnStatus" id="FilterOnStatus"
- class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
-% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
- <option value="<% $Status %>"
-% if (@FilterOnStatus && $FilterOnStatus[0]) {
- <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
-% }
- ><% loc($Status) %></option>
-% }
- </select>
- <div class="text-center">
- <input type="submit" value="<% loc('Filter') %>" class="mr-2 button btn btn-primary" />
- <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
- value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
- </div>
-</form>
-</&>
-
- <&| /Widgets/TitleBox,
- title => loc('Event Types'),
- &>
-
-<%perl>
-my %CalendarIcons = RT->Config->Get('CalendarIcons');
-# Sort the legend after translation
-my %CalendarIconsTranslated;
-my $LegendLabel;
-LEGEND:
-foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
- # We might have multiple labels for the same icon
- # such as "LastUpdated, CF.{Maintenance Date}"
- # so we need to split them and translate them individually
- my @LegendLabels = split ',', $legend;
- $LegendLabel = join ', ',
- map {
- my $label = $_;
- next LEGEND unless ( grep { $label eq $_ } @Dates );
- $_ =~ s/^\s+|\s+$//g;
- $_ =~ s/^CF\.\{(.*)\}/$1/;
- $_ = 'Last Updated' if $_ eq 'LastUpdated';
- loc($_)
- } @LegendLabels;
- $CalendarIconsTranslated{$LegendLabel} = $legend;
-}
-foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
-</%perl>
- <span class="tip">
- <span class="tipimg">
- <img
- src="<% $RT::WebImagesURL %>/<%
- $CalendarIcons{$CalendarIconsTranslated{$TranslatedLegend}}|n %>" />
- </span>
- <span class="tiplegend">
- <% $TranslatedLegend %>
- </span>
- </span>
-% }
-</&>
-
-<&| /Widgets/TitleBox,
- title => loc('State Colors'),
- &>
-% my %ColorStatusMap = RT->Config->Get('CalendarStatusColorMap');
-% foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
- <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
-% }
-</&>
-
-</div>
-</div>
+ <& /Elements/CalendarSidebar,
+ BaseQuery => $BaseQuery,
+ Month => $Month,
+ Year => $Year,
+ Format => $Format,
+ Order => $Order,
+ OrderBy => $OrderBy,
+ RowsPerPage => $RowsPerPage,
+ FilterOnStatus => \@FilterOnStatus,
+ Dates => \@Dates,
+ &>
<div class="calendar-content">
<&| /Widgets/TitleBox,
@@ -316,17 +240,6 @@ portlet by saving a query with the name <code>calendar</code> in the [_1].
</&>
</div>
-
-<script type="text/javascript">
- jQuery(document).ready(function() {
- jQuery('.calendar-toggle-sidebar').click(function() {
- jQuery('.calendar-sidebar').toggle();
- jQuery('.calendar-sidebar-toggle-content,.calendar-toggle-sidebar').toggleClass('sidebar-off');
- jQuery('.calendar-content').toggleClass('sidebar-off');
- });
- });
-</script>
-
<%INIT>
if ($FilterOnStatusClear) {
$Query = $BaseQuery if $BaseQuery;
commit b3bb84b8243463f43982efe6ddc20159c5cb4afe
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 8 16:01:52 2023 -0300
Add multievent days position mechanism
This commit adds a new mechanism to position multievent days in the
calendar. It simulates the UI of other known calendars.
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index b8023d9..681b2fb 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -2,20 +2,72 @@
$Date => undef
$Object => undef
$DateTypes => undef
+$DayOfWeek => undef
+$TicketsSpanningDays => undef
+$WeekTicketPosition => undef
+$CurrentPostion => undef
</%args>
-<div class="day">
-<small>
- <% RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder)|n %>
- <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"
+<%perl>
+my $icon = RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder);
+my $spanning_tickets_for_today = $TicketsSpanningDays->{$today} || [];
+my $spanning_tickets_for_tomorrow = $TicketsSpanningDays->{$tomorrow} || [];
+# If it has an icon and it's a spanning day of a ticket, it's the
+# last day of a spanning ticket
+my $last_day_of_spanning_ticket = 0;
+if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
+ $last_day_of_spanning_ticket = 1;
+ # This frees up the position for the next ticket
+ $WeekTicketPosition->{$CurrentPostion}->{id} = "";
+}
+</%perl>
+
+<div class="day
+% if ( $last_day_of_spanning_ticket || $DayOfWeek eq 7 ||
+% ! grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) {
+ last-day
+% }
+% if ( ($icon && ! grep { $_ eq $TicketId } @$spanning_tickets_for_today )
+% || $DayOfWeek eq 1 ) {
+ first-day
+% }
+" style="
% if ( $CalendarStatusColorMap{$status} ) {
- style="color: <%$CalendarStatusColorMap{$status}%>;"
+ background-color: <%$CalendarStatusColorMap{$status}%>;
+% }
+% if ( (grep { $_ eq $TicketId } @$spanning_tickets_for_today)
+% && $DayOfWeek ne 1 ) {
+ z-index: 3;
+% }
+" data-object="<% $Object->Type %>-<% $Object->id %>">
+
+ <small>
+ <div class="event-icon" style="
+% if ($last_day_of_spanning_ticket
+% && !($icon && ! grep { $_ eq $TicketId } @$spanning_tickets_for_today )) {
+ float: right;
% }
- >
+ ">
+% if ($icon && ! grep { $_ eq $TicketId } @$spanning_tickets_for_today ) {
+ <% $icon|n %>
+% }
+ </div>
+ <div class="event-info">
+% # If it has an icon and it's not a spanning day of a ticket, it's the
+% # first day of a spanning ticket and not the last, which is part of the
+% # spanning_tickets_for_today array.
+% if (
+% ($icon && !grep { $_ eq $TicketId } @$spanning_tickets_for_today )
+% || $DayOfWeek eq 1 ) {
+ <a class="event-title" href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
<% $Object->QueueObj->Name %> #<% $TicketId %>
<% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
- <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br />
- <span class="tip">
+ <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %>
+ </a>
+% }
+
+
+<span class="tip">
<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
<% $Object->QueueObj->Name %> #<% $TicketId %>
</a>
@@ -40,14 +92,18 @@ $DateTypes => undef
<strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
% }
-<br />
- </span>
+ <br />
+</span>
+ </div>
+</small>
+
</div>
<%init>
use RTx::Calendar;
my $today = $Date->strftime("%F");
+my $tomorrow = $Date->clone()->add(days => 1)->strftime("%F");
my $TicketId;
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index d827a68..fd2926c 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -153,27 +153,91 @@ foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
<tbody>
<tr>
-% while ($date <= $end) {
-% my @classes = ();
-% push @classes, "offmonth" if $date->month != ($Month + 1);
-% push @classes, "today" if (DateTime->compare($today, $date) == 0);
-% push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
-% push @classes, "aweekago" if (DateTime->compare($aweekago, $date) == 0);
+<%perl>
+# We use %week_ticket_position to control the display of tickets on the
+# calendar. It has the following structure:
+# {
+# 1 => { id => 123, TicketObj => $t },
+# 2 => { id => 312, TicketObj => $t },
+# 3 => { id => '', TicketObj => undef }, # empty position
+# 4 => { id => 111, TicketObj => $t },
+# }
+# where the key is the position/line of the ticket in the current week
+# when an event ends during the week, it's removed from the hash, openning
+# the position for a new ticket to be placed at the same line on the week,
+# saving some height on the calendar.
+# This variable is cleaned every time we start a new week.
+my %week_ticket_position;
+my $day_of_week = 1;
+
+while ($date <= $end) {
+ my @classes = ();
+ push @classes, "offmonth" if $date->month != ($Month + 1);
+ push @classes, "today" if (DateTime->compare($today, $date) == 0);
+ push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+ push @classes, "aweekago" if (DateTime->compare($aweekago, $date) == 0);
+
+ for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
+ # check if ticket was already displayed this week, if not, we need to find a
+ # position for it
+ unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
+ # new tickets should assume the first empty spot.
+ my $i = 1;
+ my $free_index = 0;
+ for my $index ( sort keys %week_ticket_position ) {
+ if ( $week_ticket_position{$index}{id} eq "" ) {
+ $free_index = $i;
+ last;
+ }
+ $i++;
+ }
+ # if we found a free spot, we place the ticket there
+ if ( $free_index != 0 ) {
+ $week_ticket_position{$free_index}{id} = $t->id;
+ $week_ticket_position{$free_index}{TicketObj} = $t;
+ }
+ # if not, we add it to the end of the hash
+ else {
+ $week_ticket_position{((scalar keys %week_ticket_position)+1)}{id} = $t->id;
+ $week_ticket_position{((scalar keys %week_ticket_position))}{TicketObj} = $t;
+ }
+ }
+ }
+</%perl>
<td class="<% @classes %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
-
-% for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
- <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+% for my $index ( sort keys %week_ticket_position ) {
+% if ( grep { $_->id eq $week_ticket_position{$index}{id} }
+% @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
+% my $t = $week_ticket_position{$index}{TicketObj};
+ <& /Elements/CalendarEvent,
+ Object => $t,
+ Date => $date,
+ DateTypes => \%DateTypes,
+ DayOfWeek => $day_of_week,
+ TicketsSpanningDays => $TicketsSpanningDays,
+ WeekTicketPosition => \%week_ticket_position,
+ CurrentPostion => $index,
+ &>
+% }
+% else {
+% # if there's no ticket for this position, we add an empty space
+ <div class="day"> </div>
+% }
% }
-
</div></td>
% $date = $set->next($date);
% if ( $date->day_of_week == $startday_of_week ) {
+% # we start a new week with empty positions
+% %week_ticket_position = ();
+% $day_of_week=1;
</tr><tr>
% }
-
+% else {
+% $day_of_week = $day_of_week + 1;
+% }
% }
</tr>
</tbody>
@@ -357,7 +421,7 @@ $TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->
$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
-my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
+my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
my $DownloadQueryString =
$m->comp(
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index e8038e3..9224fc2 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -7,6 +7,7 @@ use DateTime::Set;
our $VERSION = "1.07";
RT->AddStyleSheets('calendar.css');
+RT->AddJavaScript('calendar.js');
sub FirstDay {
my ( $year, $month, $matchday ) = @_;
@@ -137,12 +138,14 @@ sub FindTickets {
Timezone => 'utc'
);
$end_date_unix = $end_date_unix->Unix;
+ my $first_day = 1;
while ( $current_date->Unix <= $end_date_unix )
{
my $dateindex = LocalDate( $current_date->Unix );
push @{ $TicketsSpanningDays{$dateindex} }, $Ticket->id
- unless $TicketsSpanningDaysAlreadySeen{$dateindex}
+ unless $first_day
+ || $TicketsSpanningDaysAlreadySeen{$dateindex}
{$Ticket}++;
push @{ $Tickets{$dateindex } },
$Ticket
@@ -153,6 +156,7 @@ sub FindTickets {
{$Ticket}++;
$current_date->AddDay();
+ $first_day = 0;
}
}
}
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 96e9086..37858ac 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -2,9 +2,17 @@
table.rtxcalendar .day {
position: relative;
z-index: 1;
+ padding: 3px 3px 3px 6px;
+ margin-top: 4px;
+ margin-bottom: 4px;
+ width:120%;
+ height: 1.75rem;
+ z-index: 4;
}
-
+table.rtxcalendar .day.last-day {
+ width: 100%;
+}
table.rtxcalendar .day:hover {
z-index: 5;
@@ -14,10 +22,10 @@ table.rtxcalendar .day span.tip {
display: none;
text-align: left;
}
-table.rtxcalendar div.day:hover span.tip{
+table.rtxcalendar div.day div.event-info:hover span.tip{
display: block;
position: absolute;
- top:12px; left:24px; width:350px;
+ top:1rem; left:24px; width:350px;
border: 1px solid #555;
background-color: #fff;
padding: 4px;
@@ -137,3 +145,28 @@ a.calendar-toggle-sidebar.sidebar-off::before {
.calendar-sidebar {
margin-right: 10px;
}
+
+.event-icon {
+ float: left;
+ margin-right: 5px;
+}
+
+.event-info a.event-title {
+ overflow: hidden;
+ max-width: 100%;
+ left: 0px;
+ padding-left: 21px;
+ white-space: nowrap;
+ position: absolute;
+ color: white;
+}
+
+table.rtxcalendar .day.first-day {
+ border-radius: 5px 0 0 5px;
+}
+table.rtxcalendar .day.last-day {
+ border-radius: 0 5px 5px 0;
+}
+table.rtxcalendar .day.first-day.last-day {
+ border-radius: 5px;
+}
diff --git a/static/js/calendar.js b/static/js/calendar.js
new file mode 100644
index 0000000..f478677
--- /dev/null
+++ b/static/js/calendar.js
@@ -0,0 +1,30 @@
+jQuery(function() {
+
+ /* Adjust the width of the event title according to the number spanning
+ * days of an event for each week of the calendar (including MyCalendar
+ * portlet).
+ * */
+ jQuery('.rtxcalendar').find('tr').each(
+ function(i, tr){
+ var event_repetions_on_week = {};
+ /* Each event day (first and spanning) is marked with the
+ * data-object attribute in a format like ticket-123 */
+ jQuery(tr).find('[data-object]').each(function(j, event_day){
+ if (event_repetions_on_week[jQuery(event_day).attr('data-object')] == undefined){
+ event_repetions_on_week[jQuery(event_day).attr('data-object')] = 1;
+ } else {
+ event_repetions_on_week[jQuery(event_day).attr('data-object')]++;
+ }
+ })
+ for (var key in event_repetions_on_week){
+ // Find the title of the first day of the event and adjust the max-width
+ jQuery(tr).find('.first-day[data-object="' + key + '"]')
+ .each(function(x, first_event_day){
+ jQuery(first_event_day).find('.event-title')
+ .css('max-width', event_repetions_on_week[key] * 100 + '%');
+ })
+ }
+ console.log(event_repetions_on_week);
+ }
+ )
+});
commit 7b394619836a68ae232381a12f1cbabe0153aa54
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Thu Sep 7 18:26:10 2023 -0300
Add Multiple Days Events to Calendar
Add Multiple Days Events to Calendar where an event can span multiple
based on customizable start and end time fields.
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index 5220934..f95cc9d 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -23,7 +23,7 @@
<td class="<% @classes %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
-% for my $t ( $SortCalendarEvents->( @{ $Tickets{ $date->strftime("%F") } || [] } )) {
+% for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
<& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
% }
@@ -95,7 +95,7 @@ $Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->str
$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$Query, FormatRef => \$Format );
-my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 5ba02af..d827a68 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -163,7 +163,7 @@ foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
<td class="<% @classes %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
-% for my $t ( $SortCalendarEvents->( @{ $Tickets{ $date->strftime("%F") } || [] } )) {
+% for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
<& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
% }
@@ -357,7 +357,7 @@ $TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->
$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
-my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
+my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
my $DownloadQueryString =
$m->comp(
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 8120ece..e8038e3 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -49,6 +49,24 @@ sub DatesClauses {
my @DateClauses = map {
"($_ >= '" . $begin . " 00:00:00' AND $_ <= '" . $end . " 23:59:59')"
} @$Dates;
+
+ # All multiple days events are already covered on the query above
+ # The following code works for covering events that start before and ends
+ # after the selected period
+ my $multiple_days_events = RT->Config->Get('CalendarMultipleDaysEvents');
+ for my $event ( keys %$multiple_days_events ) {
+ push @DateClauses,
+ "("
+ . $multiple_days_events->{$event}{Starts}
+ . " <= '"
+ . $end
+ . " 00:00:00' AND "
+ . $multiple_days_events->{$event}{Ends}
+ . " >= '"
+ . $begin
+ . " 23:59:59')";
+ }
+
$clauses .= " AND " . " ( " . join( " OR ", @DateClauses ) . " ) "
if @DateClauses;
@@ -58,6 +76,15 @@ sub DatesClauses {
sub FindTickets {
my ( $CurrentUser, $Query, $Dates, $begin, $end ) = @_;
+ my $multiple_days_events = RT->Config->Get('CalendarMultipleDaysEvents');
+ my @multiple_days_fields;
+ for my $event ( keys %$multiple_days_events ) {
+ for my $type ( keys %{ $multiple_days_events->{$event} } ) {
+ push @multiple_days_fields,
+ $multiple_days_events->{$event}{$type};
+ }
+ }
+
$Query .= DatesClauses( $Dates, $begin, $end )
if $begin and $end;
@@ -66,40 +93,17 @@ sub FindTickets {
my %Tickets;
my %AlreadySeen;
+ my %TicketsSpanningDays;
+ my %TicketsSpanningDaysAlreadySeen;
while ( my $Ticket = $Tickets->Next() ) {
-
# How to find the LastContacted date ?
+ # Find single day events fields
for my $Date (@$Dates) {
-
# $dateindex is the date to use as key in the Tickets Hash
# in the YYYY-MM-DD format
# Tickets are then groupd by date in the %Tickets hash
- my $dateindex;
- if ($Date =~ /^CF\./){
- my $cf = $Date;
- $cf =~ s/^CF\.\{(.*)\}/$1/;
-
- my $CFDateValue = $Ticket->FirstCustomFieldValue($cf);
- next unless $CFDateValue;
- my $CustomFieldObj = RT::CustomField->new($CurrentUser);
- $CustomFieldObj->LoadByName( Name => $cf );
- my $CustomFieldObjType = $CustomFieldObj->Type;
- my $DateObj = RT::Date->new($CurrentUser);
- if ( $CustomFieldObjType eq 'Date' ) {
- $DateObj->Set(
- Format => 'unknown',
- Value => $CFDateValue,
- Timezone => 'utc'
- );
- } else {
- $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
- }
- $dateindex = LocalDate( $DateObj->Unix );
- } else {
- my $DateObj = $Date . "Obj";
- $dateindex = LocalDate( $Ticket->$DateObj->Unix );
- }
+ my $dateindex = _GetDate( $Date, $Ticket, $CurrentUser );
push @{ $Tickets{$dateindex } },
$Ticket
@@ -110,8 +114,84 @@ sub FindTickets {
or $AlreadySeen{ $dateindex }
{$Ticket}++;
}
+
+ # Find spanning days of multiple days events
+ for my $event (sort keys %$multiple_days_events) {
+ my $starts_field = $multiple_days_events->{$event}{'Starts'};
+ my $ends_field = $multiple_days_events->{$event}{'Ends'};
+ my $starts_date = _GetDate( $starts_field, $Ticket, $CurrentUser );
+ my $ends_date = _GetDate( $ends_field, $Ticket, $CurrentUser );
+
+ # Loop through all days between start and end and add the ticket
+ # to it
+ my $current_date = RT::Date->new($CurrentUser);
+ $current_date->Set(
+ Format => 'unknown',
+ Value => $starts_date,
+ Timezone => 'utc'
+ );
+ my $end_date_unix = RT::Date->new($CurrentUser);
+ $end_date_unix->Set(
+ Format => 'unknown',
+ Value => $ends_date,
+ Timezone => 'utc'
+ );
+ $end_date_unix = $end_date_unix->Unix;
+ while ( $current_date->Unix <= $end_date_unix )
+ {
+ my $dateindex = LocalDate( $current_date->Unix );
+
+ push @{ $TicketsSpanningDays{$dateindex} }, $Ticket->id
+ unless $TicketsSpanningDaysAlreadySeen{$dateindex}
+ {$Ticket}++;
+ push @{ $Tickets{$dateindex } },
+ $Ticket
+ # if reminder, check it's refering to a ticket
+ unless ( $Ticket->Type eq 'reminder'
+ and not $Ticket->RefersTo->First )
+ or $AlreadySeen{ $dateindex }
+ {$Ticket}++;
+
+ $current_date->AddDay();
+ }
+ }
+ }
+ if ( wantarray ) {
+ return ( \%Tickets, \%TicketsSpanningDays );
+ } else {
+ return \%Tickets;
+ }
+}
+
+sub _GetDate {
+ my $date_field = shift;
+ my $Ticket = shift;
+ my $CurrentUser = shift;
+
+ if ($date_field =~ /^CF\./){
+ my $cf = $date_field;
+ $cf =~ s/^CF\.\{(.*)\}/$1/;
+
+ my $CFDateValue = $Ticket->FirstCustomFieldValue($cf);
+ next unless $CFDateValue;
+ my $CustomFieldObj = RT::CustomField->new($CurrentUser);
+ $CustomFieldObj->LoadByName( Name => $cf );
+ my $CustomFieldObjType = $CustomFieldObj->Type;
+ my $DateObj = RT::Date->new($CurrentUser);
+ if ( $CustomFieldObjType eq 'Date' ) {
+ $DateObj->Set(
+ Format => 'unknown',
+ Value => $CFDateValue,
+ Timezone => 'utc'
+ );
+ } else {
+ $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
+ }
+ return LocalDate( $DateObj->Unix );
+ } else {
+ my $DateObj = $date_field . "Obj";
+ return LocalDate( $Ticket->$DateObj->Unix );
}
- return %Tickets;
}
#
@@ -355,6 +435,19 @@ C<$CalendarIcons> setting to your F<etc/RT_SiteConfig.pm>:
The images should be placed on F<local/static/images>.
+=head3 Multiple days events
+
+You can define multiple days events by adding the C<%CalendarMultipleDaysEvents>
+setting to your F<etc/RT_SiteConfig.pm>:
+
+ Set( %CalendarMultipleDaysEvents, (
+ 'Maintenance' => {
+ 'Starts' => 'Starts',
+ 'Ends' => 'Due',
+ },
+ )
+ );
+
=head1 USAGE
A small help section is available in /Search/Calendar.html
-----------------------------------------------------------------------
hooks/post-receive
--
rtx-calendar
More information about the Bps-public-commit
mailing list