[Bps-public-commit] rtx-calendar branch multiple-days-events created. 1.05-45-gd2fff9f
BPS Git Server
git at git.bestpractical.com
Mon Oct 16 20:31:50 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 d2fff9f8a875dfe5ae847d371b1710233cf7dad0 (commit)
- Log -----------------------------------------------------------------
commit d2fff9f8a875dfe5ae847d371b1710233cf7dad0
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Mon Oct 16 17:25:56 2023 -0300
Reintroduce Sorting Events config
165c2735 removed the sorting events config, because it was causing an
error when sorting tickets on different calendar Formats. That patch
introduced a simple sorting algorithm that sorts by the first date field
of the Format.
However, there are situations where the first date field is a custom
field, and the sorting by this simple mechanism can lead to a heavy
performance penalty.
This patch reintroduces the sorting events config, allowing us to use
different fields to sort the events rather than the first date field.
diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 5241e22..56631a0 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -22,4 +22,19 @@ Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
Set(@CalendarFilterDefaultStatuses, qw(new open));
+Set($CalendarSortEvents, sub {
+ my $tickets_of_the_day = shift;
+ # This field is the first date field from the format attribute
+ # We receive it but it don't need to use it necessarily as in this
+ # example
+ my $sorting_field = shift;
+ my $current_user = shift;
+ my @sorted_tickets = sort {
+ $a_value = $a->id;
+ $b_value = $b->id;
+ ($a_value cmp $b_value)
+ } @$tickets_of_the_day;
+ return @sorted_tickets;
+});
+
1;
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index b6bf784..67bc3f0 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -87,10 +87,10 @@ while ($date <= $end) {
push @classes, "aweekago" if (DateTime->compare($aweekago, $date) == 0);
push @classes, "weekday-$day_of_week";
- for my $t ( RTx::Calendar::SortCalendarEvents(
- \@{ $Tickets->{ $date->strftime("%F") } || [] },
- $sorting_field,
- $session{CurrentUser} ) ) {
+ for my $t ( $SortCalendarEvents->( \@{ $Tickets->{ $date->strftime("%F") } || [] },
+ $sorting_field,
+ $session{CurrentUser} ))
+ {
# 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 ) {
@@ -331,5 +331,6 @@ my $DownloadQueryString =
OrderBy => $OrderBy,
);
+my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
my $sorting_field = $Dates[0] || '';
</%INIT>
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index da45a4e..8156613 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -24,10 +24,10 @@ while ($date <= $end) {
push @classes, "today" if (DateTime->compare($today, $date) == 0);
push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
push @classes, "weekday-$day_of_week";
- for my $t ( RTx::Calendar::SortCalendarEvents(
- \@{ $Tickets->{ $date->strftime("%F") } || [] },
- $sorting_field,
- $session{CurrentUser} ) ) {
+ for my $t ( $SortCalendarEvents->( \@{ $Tickets->{ $date->strftime("%F") } || [] },
+ $sorting_field,
+ $session{CurrentUser} ))
+ {
# 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 ) {
@@ -147,5 +147,6 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
my $sorting_field = $Dates[0] || '';
</%INIT>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 94aef5d..afcd7ff 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -210,19 +210,6 @@ sub GetDate {
}
}
-sub SortCalendarEvents {
- my $tickets_of_the_day = shift;
- my $sorting_field = shift;
- my $current_user = shift;
- my @sorted_tickets = sort {
- my ($a_value, $b_value);
- $a_value = RTx::Calendar::GetDate( $sorting_field, $a, $current_user ) . " " . $a->id;
- $b_value = RTx::Calendar::GetDate( $sorting_field, $b, $current_user ) . " " . $b->id;
- ($a_value cmp $b_value)
- } @$tickets_of_the_day;
- return @sorted_tickets;
-}
-
#
# Take a user object and return the search with Description "calendar" if it exists
#
@@ -487,6 +474,43 @@ setting to your F<etc/RT_SiteConfig.pm>:
Note that the Starts and Ends fields must be included in the search result
Format in order the event to be displayed on the calendar.
+=head3 Event sorting
+
+You can set the order that the events will presented in the day cell with
+the C<$CalendarSortEvents> setting.
+
+This setting takes a subroutine reference that receives:
+
+=over
+
+=item an array of L<RT::Ticket> objects
+
+=item the first date field from the format attribute
+
+=item the current user object
+
+=back
+
+It must return a sorted array of L<RT::Ticket>.
+
+The following example sorts the events by status:
+
+ Set($CalendarSortEvents, sub {
+ my $tickets_of_the_day = shift;
+ my $sorting_field = shift;
+ my $current_user = shift;
+ my @sorted_tickets = sort {
+ $a_value = $a->id;
+ $b_value = $b->id;
+ ($a_value cmp $b_value)
+ } @$tickets_of_the_day;
+ return @sorted_tickets;
+ });
+
+Important: The usage of Custom Fields to sort the events can be heavy on
+the loading time of the calendar. If you have a lot of tickets, it's
+recommended to use the default sorting.
+
=head1 USAGE
A small help section is available in /Search/Calendar.html
commit a4c03a6dc705ad3fabcc9cd0c96d17d16dc918f9
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Mon Oct 16 11:37:55 2023 -0300
Load event details with Ajax call when hovering event title
For improving performance, the event details are loaded now with an Ajax
call when hovering the event title.
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 515e1e0..d06e38c 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -67,46 +67,9 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
<% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
<% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %>
</a>
+% # Placeholder for the event details that will be loaded via AJAX on hover
<span class="tip">
- <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
- <% $Object->QueueObj->Name %> #<% $TicketId %>
- </a>
- :</strong> <% $subject%><br />
- <br />
-
-<%perl>
-# logic taken from Ticket/Search/Results.tsv
- foreach my $attr (@display_fields) {
- my $value;
-
- if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
- $value = '-';
- } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
- my $cf = $1;
- my $cf_obj = $Object->LoadCustomFieldByIdentifier($cf);
- unless ($cf_obj->id) {
- $RT::Logger->debug("Custom field '$cf' not available for ticket #".$Object->id);
- next;
- }
- $value = $Object->FirstCustomFieldValue($cf);
- if (grep { $_ eq $cf_obj->Type} qw(DateTime Date)) {
- my $date_value = RT::Date->new($session{'CurrentUser'});
- my $date_format = $cf_obj->Type eq 'DateTime' ? 'ISO' : 'unknown';
- $date_value->Set(Format => $date_format, Value => $value);
- $value = $date_value->AsString( Timezone => 'user', Time => $cf_obj->Type eq 'DateTime' ? 1 : 0 );
- }
- } else {
- my $method = '$Object->'.$attr.'()';
- $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
- $value = eval $method;
- if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
- }
-</%perl>
- <strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
-% }
-
- <br />
-</span>
+ </span>
% }
</div>
</small>
@@ -146,31 +109,4 @@ my $display_owner = $RT::CalendarDisplayOwner;
$display_owner ||= RT->Config->Get('CalendarDisplayOwner')
if RT->can('Config');
-
-# 3.6 config
-my @display_fields = @RT::CalendarPopupFields;
-
-# 3.8 config
-# the if condition is weird but it doesn't work with 3.8.0 without the last part
- at display_fields = RT->Config->Get('CalendarPopupFields')
- if 0 == @display_fields and RT->can('Config') and RT->Config->Get('CalendarPopupFields');
-
-# default
-if (0 == @display_fields) {
- @display_fields = qw(OwnerObj->Name CreatedObj->ISO StartsObj->ISO
- StartedObj->ISO LastUpdatedObj->ISO DueObj->ISO
- ResolvedObj->ISO Status Priority
- Requestors->MemberEmailAddressesAsString);
-}
-
-
-my %label_of;
-for my $field (@display_fields) {
- my $label = $field;
- $label =~ s'Obj-.(?:AsString|Name|ISO)''g;
- $label =~ s'-\>MemberEmailAddressesAsString''g;
- $label =~ s/CustomField\.\{(.*)\}/$1/g;
- $label_of{$field} = $label;
-}
-
</%init>
diff --git a/html/Helpers/CalendarEventInfo b/html/Helpers/CalendarEventInfo
new file mode 100644
index 0000000..debc8d3
--- /dev/null
+++ b/html/Helpers/CalendarEventInfo
@@ -0,0 +1,89 @@
+<%args>
+$event
+</%args>
+<%init>
+my @event_details = split /-/, $event;
+my $object_type = $event_details[0]; # ticket or reminder
+my $ticket_id = $event_details[1];
+my $Object = RT::Ticket->new($session{'CurrentUser'});
+$Object->Load($ticket_id);
+
+my $status;
+my $TicketId;
+my $subject = $Object->Subject;
+
+if ($Object->Type eq 'reminder') {
+ if ($Object->RefersTo->First) {
+ my $ticket = $Object->RefersTo->First->TargetObj;
+ $TicketId = $ticket->Id;
+ $subject = $Object->Subject . " (" . $ticket->Subject . ")";
+ $status = $Object->Status;
+ }
+} else {
+ $TicketId = $Object->Id;
+ $subject = $Object->Subject;
+ $status = $Object->Status;
+}
+
+# 3.6 config
+my @display_fields = @RT::CalendarPopupFields;
+
+# 3.8 config
+# the if condition is weird but it doesn't work with 3.8.0 without the last part
+ at display_fields = RT->Config->Get('CalendarPopupFields')
+ if 0 == @display_fields and RT->can('Config') and RT->Config->Get('CalendarPopupFields');
+
+# default
+if (0 == @display_fields) {
+ @display_fields = qw(OwnerObj->Name CreatedObj->ISO StartsObj->ISO
+ StartedObj->ISO LastUpdatedObj->ISO DueObj->ISO
+ ResolvedObj->ISO Status Priority
+ Requestors->MemberEmailAddressesAsString);
+}
+
+my %label_of;
+for my $field (@display_fields) {
+ my $label = $field;
+ $label =~ s'Obj-.(?:AsString|Name|ISO)''g;
+ $label =~ s'-\>MemberEmailAddressesAsString''g;
+ $label =~ s/CustomField\.\{(.*)\}/$1/g;
+ $label_of{$field} = $label;
+}
+</%init>
+<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+ <% $Object->QueueObj->Name %> #<% $TicketId %>
+</a>
+:</strong> <% $subject%><br />
+<br />
+<%perl>
+# logic taken from Ticket/Search/Results.tsv
+foreach my $attr (@display_fields) {
+ my $value;
+
+ if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
+ $value = '-';
+ } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
+ my $cf = $1;
+ my $cf_obj = $Object->LoadCustomFieldByIdentifier($cf);
+ unless ($cf_obj->id) {
+ $RT::Logger->debug("Custom field '$cf' not available for ticket #".$Object->id);
+ next;
+ }
+ $value = $Object->FirstCustomFieldValue($cf);
+ if (grep { $_ eq $cf_obj->Type} qw(DateTime Date)) {
+ my $date_value = RT::Date->new($session{'CurrentUser'});
+ my $date_format = $cf_obj->Type eq 'DateTime' ? 'ISO' : 'unknown';
+ $date_value->Set(Format => $date_format, Value => $value);
+ $value = $date_value->AsString( Timezone => 'user', Time => $cf_obj->Type eq 'DateTime' ? 1 : 0 );
+ }
+ } else {
+ my $method = '$Object->'.$attr.'()';
+ $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
+ $value = eval $method;
+ if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
+ }
+</%perl>
+ <strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
+% }
+<br />
+% $m->abort;
diff --git a/static/js/calendar.js b/static/js/calendar.js
index 07a3958..ae9c819 100644
--- a/static/js/calendar.js
+++ b/static/js/calendar.js
@@ -7,6 +7,11 @@ jQuery(function() {
changeCalendarMonth();
});
+ jQuery('div[data-object]>small>div.event-info>a.event-title').hover(
+ function(e) {
+ loadCalendarEventDetails(e);
+ }
+ );
});
/*
@@ -52,3 +57,20 @@ function changeCalendarMonth() {
var querystring = jQuery('.changeCalendarMonth #querystring').val();
window.location.href = "?Month=" + month + "&Year=" + year + "&" + querystring;
}
+
+function loadCalendarEventDetails(e) {
+ // data-object
+ var event = jQuery(e.currentTarget).parents('[data-object]').attr('data-object');
+ // remove hover event from the element to run only once
+ jQuery(e.currentTarget).off('mouseenter mouseleave');
+
+ var url = RT.Config.WebHomePath + '/Helpers/CalendarEventInfo?event=' + event;
+
+ jQuery.ajax({
+ url: url,
+ success: function(data) {
+ jQuery(e.currentTarget).parents('[data-object]')
+ .find('div.event-info>span.tip').html(data);
+ }
+ });
+}
commit 068faddf0d6b099476a8d3d068935436df751af4
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Mon Oct 16 09:40:50 2023 -0300
Change date loading from unknown to ISO for better performance
As described on https://docs.bestpractical.com/rt/5.0.4/RT/Date.html#Set
loading a date from an unknown format is a "heavyweight" operation as
for now.
This patch updates the code to load dates from ISO format.
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index e5f3bfb..94aef5d 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -107,7 +107,7 @@ sub FindTickets {
# in the YYYY-MM-DD format
# Tickets are then groupd by date in the %Tickets hash
my $dateindex = GetDate( $Date, $Ticket, $CurrentUser );
-
+ next unless $dateindex;
push @{ $Tickets{$dateindex } },
$Ticket
@@ -128,19 +128,19 @@ sub FindTickets {
my $ends_field = $multiple_days_events->{$event}{'Ends'};
my $starts_date = GetDate( $starts_field, $Ticket, $CurrentUser );
my $ends_date = GetDate( $ends_field, $Ticket, $CurrentUser );
-
+ next unless $starts_date and $ends_date;
# 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,
+ Format => 'ISO',
+ Value => $starts_date . " 00:00:00",
Timezone => 'user'
);
my $end_date_unix = RT::Date->new($CurrentUser);
$end_date_unix->Set(
- Format => 'unknown',
- Value => $ends_date,
+ Format => 'ISO',
+ Value => $ends_date . " 00:00:00",
Timezone => 'user'
);
$end_date_unix = $end_date_unix->Unix;
@@ -197,8 +197,8 @@ sub GetDate {
my $DateObj = RT::Date->new($CurrentUser);
if ( $CustomFieldObjType eq 'Date' ) {
$DateObj->Set(
- Format => 'unknown',
- Value => $CFDateValue,
+ Format => 'ISO',
+ Value => $CFDateValue . " 00:00:00",
);
} else {
$DateObj->Set( Format => 'ISO', Value => $CFDateValue );
@@ -298,14 +298,15 @@ CALENDAR_ICON:
} elsif ( $DateField =~ /^CF\./ ) {
my $cf = $DateField;
$cf =~ s/^CF\.\{(.*)\}/$1/;
+ my $CustomFieldObj = $Object->LoadCustomFieldByIdentifier($cf);
+ next CALENDAR_ICON unless $CustomFieldObj->id;
my $DateValue = $Object->FirstCustomFieldValue($cf);
next CALENDAR_ICON unless $DateValue;
- my $DateObj = RT::Date->new( $CurrentUser );
- my $CustomFieldObj = RT::CustomField->new( $CurrentUser );
- $CustomFieldObj->LoadByName( Name => $cf );
- my $date_format = $CustomFieldObj->Type eq 'Date' ? 'unknown' : 'ISO';
- $DateObj->Set( Format => $date_format, Value => $DateValue );
- $DateValue = $DateObj->ISO( Time => 0, Timezone => 'user' );
+ unless ( $CustomFieldObj->Type eq 'Date' ) {
+ my $DateObj = RT::Date->new( $CurrentUser );
+ $DateObj->Set( Format => 'ISO', Value => $DateValue );
+ $DateValue = $DateObj->ISO( Time => 0, Timezone => 'user' );
+ }
next CALENDAR_ICON unless $DateValue eq $CurrentDate;
} else {
my $DateObj = $ComparedDate . "Obj";
commit 0b8436bfbd5d0f99f95b867e832a818416c193c7
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Thu Oct 12 17:51:09 2023 -0300
Change icon to be rendered only when required
Icon was being rendered even when there was no need for it. This commit
changes the code to render the icon only when it is the first or the
last day of the event.
Update the identation of the code to match the rest of the file due to
the change of some if statements position.
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index df500b9..515e1e0 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -9,9 +9,6 @@ $CurrentPostion => undef
</%args>
<%perl>
-my $icon
-= RTx::Calendar::GetEventImg( $Object, $today, $DateTypes, $IsReminder,
- $session{'CurrentUser'} );
my $spanning_tickets_for_today = $TicketsSpanningDays->{$today} || [];
my $spanning_tickets_for_tomorrow = $TicketsSpanningDays->{$tomorrow} || [];
my $first_day_of_the_event = 0;
@@ -58,7 +55,10 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
float: right;
% }
">
+% if ( $first_day_of_the_event || $last_day_of_the_event ) {
+% my $icon = RTx::Calendar::GetEventImg( $Object, $today, $DateTypes, $IsReminder, $session{'CurrentUser'} );
<% $icon|n %>
+% }
</div>
<div class="event-info">
% if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
@@ -67,14 +67,11 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
<% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
<% 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>
- :</strong> <% $subject%><br />
+ <span class="tip">
+ <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+ <% $Object->QueueObj->Name %> #<% $TicketId %>
+ </a>
+ :</strong> <% $subject%><br />
<br />
<%perl>
@@ -110,6 +107,7 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
<br />
</span>
+% }
</div>
</small>
commit 11c8a53bdbef7483f06e7587b1de6696464f6d22
Author: Jason Crome <jcrome at bestpractical.com>
Date: Wed Oct 4 16:47:40 2023 -0400
RTx-Calendar 1.10
diff --git a/CHANGES b/CHANGES
index ef176b3..fb3422a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+1.10 2023-10-04
+ - Sort color legend and filter by status values the same way, using
+ a case-insensitive sort.
+
1.09 2023-09-29
- Add dark mode support
- Fix bug that reset filter settings when changing months
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index c5dc443..e5f3bfb 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -4,7 +4,7 @@ use strict;
use DateTime;
use DateTime::Set;
-our $VERSION = "1.09";
+our $VERSION = "1.10";
RT->AddStyleSheets('calendar.css');
RT->AddJavaScript('calendar.js');
commit 1df07ea16042ba5861d4aca63467a4d4b29a6d5e
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Wed Oct 4 16:18:28 2023 -0300
Filter status values same as color legend
The sort order of filter on status values was different from the sort
order of the color legend, causing confusion amongst users. Both use the
same sorting mechanism now, by converting status values to lower-case
before sorting.
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index db84e96..29e68dc 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -14,7 +14,7 @@
method="post">
<select name="NewFilterOnStatus" id="NewFilterOnStatus"
class="selectpicker form-control filteronstatus mt-3 mb-3" multiple="multiple" size="6">
-% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
+% for my $Status (sort {lc(loc($a)) cmp lc(loc($b))} @{RT->Config->Get('CalendarFilterStatuses')}) {
<option value="<% $Status %>"
% if (@FilterOnStatus && $FilterOnStatus[0]) {
<% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
commit 22c5c0bbf06a55141f82b13d6079bab6e36f21ae
Author: Jason Crome <jcrome at bestpractical.com>
Date: Fri Sep 29 16:26:07 2023 -0400
RTx-Calendar 1.09
diff --git a/CHANGES b/CHANGES
index bcb6591..ef176b3 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+1.09 2023-09-29
+ - Add dark mode support
+ - Fix bug that reset filter settings when changing months
+
1.08 2023-09-27
- Add multi-day event support
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index b6b1d42..c5dc443 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -4,7 +4,7 @@ use strict;
use DateTime;
use DateTime::Set;
-our $VERSION = "1.08";
+our $VERSION = "1.09";
RT->AddStyleSheets('calendar.css');
RT->AddJavaScript('calendar.js');
commit a1f147d4982a2590479ee98e24b5387d3af0613d
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 29 14:33:22 2023 -0300
Fix losing Filter on Status when changin the month
There was a bug where the generated querystring that is used to carry
the status filter from month to month was not being generated. This
patch fixes it.
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index 8de3932..b6bf784 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -284,8 +284,7 @@ my $QueryString =
OrderBy => $OrderBy,
Rows => $RowsPerPage,
NotFirstAccess => $NotFirstAccess,
- )
- if ($Query);
+ );
$QueryString ||= 'NewQuery=1&NotFirstAccess=1';
commit b5c9d29eef73606975dbcd732deedae75eacbe07
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 29 12:14:00 2023 -0300
Add Dark Mode to the Calendar
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index b87cd78..8de3932 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -165,13 +165,13 @@ while ($date <= $end) {
<td valign="top" align="center">
<form method="post" class="changeCalendarMonth" onsubmit="changeCalendarMonth()">
-<select name="Month" class="selectpicker">
+<select name="Month" class="selectpicker form-control col-3">
% for (0..11) {
<option value="<%$_%>" <% $_ == $Month ? 'selected' : ''%> ><%$rtdate->GetMonth($_)%></option>
% }
</select>
% my $year = (localtime)[5] + 1900;
-<select name="Year" class="selectpicker">
+<select name="Year" class="selectpicker form-control col-3">
% for ( ($year-5) .. ($year+5)) {
<option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
% }
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 5f018b3..df500b9 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -35,9 +35,12 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
% if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
first-day
% }
+% my $status_class = 'event-status-'.$status;
+% $status_class =~ s/\s+/-/g;
+<% $status_class %>
" style="
% if ( $CalendarStatusColorMap{$status} ) {
- background-color: <%$CalendarStatusColorMap{$status}%>;
+ background-color: <%$CalendarStatusColorMap{$status}%> !important;
% }
% # We need to decrease the z-index of the spanning days of an event
% # so the event title (which is placed on the div of the first day of the
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index b1f96ce..db84e96 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -13,7 +13,7 @@
<form id="FilterOnStatusForm"
method="post">
<select name="NewFilterOnStatus" id="NewFilterOnStatus"
- class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
+ class="selectpicker form-control 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]) {
@@ -25,7 +25,7 @@
<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>
+ value="1" class="button btn btn-primary form-control"><% loc('Clear Filter') %></button>
</div>
</form>
</&>
@@ -52,7 +52,7 @@
&>
% 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 />
+ <span style="color: <% $ColorStatusMap{$Status} %> !important;"><% $Status %></span><br />
% }
</&>
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 915a0b0..83b740c 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -180,3 +180,17 @@ table.rtxcalendar .day.first-day.last-day {
.calendar-container {
display: flow-root;
}
+
+.darkmode table.rtxcalendar * {
+ background: unset !important;
+}
+
+.darkmode table.rtxcalendar div.day div.event-info:hover span.tip{
+ border: 1px solid #555;
+ background-color: #2C3539 !important;
+ color: #fff !important;
+}
+
+.darkmode table.rtxcalendar div.day a {
+ color: #fff !important;
+}
commit ebbe1024d1635e3c2b894ac36ae050c88b583bfb
Author: Jason Crome <jcrome at bestpractical.com>
Date: Wed Sep 27 13:17:49 2023 -0400
RTx-Calendar 1.08
diff --git a/CHANGES b/CHANGES
index 7c4a6a2..bcb6591 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+1.08 2023-09-27
+ - Add multi-day event support
+
1.07 2023-09-06
- Clicking Reset Filter no longer resets month display
diff --git a/MANIFEST b/MANIFEST
index c9e48ba..0b407dd 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,8 +1,11 @@
CHANGES
etc/RTxCalendar_Config.pm
-etc/tabs_privileged_callback.patch
html/Callbacks/RTx-Calendar/Elements/Tabs/Privileged
+html/Elements/Calendar
html/Elements/CalendarEvent
+html/Elements/CalendarFooter
+html/Elements/CalendarSidebar
+html/Elements/CalendarWithSidebar
html/Elements/MyCalendar
html/Search/Calendar.html
inc/Module/Install.pm
@@ -33,4 +36,5 @@ static/images/started.png
static/images/starts.png
static/images/starts_due.png
static/images/updated.png
+static/js/calendar.js
TODO
diff --git a/META.yml b/META.yml
index 9215247..fe5224e 100644
--- a/META.yml
+++ b/META.yml
@@ -26,6 +26,6 @@ requires:
perl: 5.10.1
resources:
license: http://opensource.org/licenses/gpl-license.php
-version: '1.07'
+version: '1.08'
x_module_install_rtx_version: '0.43'
x_requires_rt: 5.0.0
diff --git a/README b/README
index dc900c5..83a757d 100644
--- a/README
+++ b/README
@@ -28,10 +28,6 @@ INSTALLATION
May need root permissions
patch RT
- Apply for versions prior to 4.4.2:
-
- patch -p1 -d /path/to/rt < etc/tabs_privileged_callback.patch
-
Edit your /opt/rt5/etc/RT_SiteConfig.pm
Add this line:
@@ -43,11 +39,19 @@ INSTALLATION
Restart your webserver
CONFIGURATION
- Base configuration
- To use the MyCalendar portlet, you must add MyCalendar to
+ Use the calendar on Dashboard
+ The calendar comes with 3 different portlets that can be added to your
+ RT dashboards:
+
+ MyCalendar, a summary of the events for the current week.
+ Calendar, a full month of the calendar view, without sidebar.
+ CalendarWithSidebar, a full month of the calendar view, with sidebar
+ which includes an extra status filter and legends of the calendars.
+
$HomepageComponents in etc/RT_SiteConfig.pm:
- Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
+ Set($HomepageComponents, [qw(QuickCreate Quicksearch
+ MyCalendar Calendar CalendarWithSidebar
MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
Display configuration
@@ -67,21 +71,6 @@ CONFIGURATION
'DueObj->ISO',
'CustomField.{Maintenance Estimated Start Date/Time - ET}'));
- Event sorting
- You can set the order that the events will presented in the day cell
- with the $CalendarSortEvents setting.
-
- This setting takes a subroutine reference that will receive an array of
- RT::Ticket objects and should return a sorted array of RT::Ticket.
-
- The following example sorts the events by status:
-
- Set($CalendarSortEvents, sub {
- my @Tickets = @_;
- my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
- return @SortedTickets;
- });
-
Event colors
It's also possible to change the color of the events in the calendar by
adding the $CalendarStatusColorMap setting to your etc/RT_SiteConfig.pm:
@@ -103,6 +92,12 @@ CONFIGURATION
Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+ Default selected status on Filtering on Status field
+ You can change the default selected statuses by adding them to the
+ @CalendarFilterDefaultStatuses setting to your etc/RT_SiteConfig.pm:
+
+ Set(@CalendarFilterDefaultStatuses, qw(new open));
+
Custom icons
Custom Icons can be defined for the events in the calendar by adding the
$CalendarIcons setting to your etc/RT_SiteConfig.pm:
@@ -114,6 +109,21 @@ CONFIGURATION
The images should be placed on local/static/images.
+ Multiple days events
+ You can define multiple days events by adding the
+ %CalendarMultipleDaysEvents setting to your etc/RT_SiteConfig.pm:
+
+ Set( %CalendarMultipleDaysEvents, (
+ 'Maintenance' => {
+ 'Starts' => 'Starts',
+ 'Ends' => 'Due',
+ },
+ )
+ );
+
+ Note that the Starts and Ends fields must be included in the search
+ result Format in order the event to be displayed on the calendar.
+
USAGE
A small help section is available in /Search/Calendar.html
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 0ac7da8..b6b1d42 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -4,7 +4,7 @@ use strict;
use DateTime;
use DateTime::Set;
-our $VERSION = "1.07";
+our $VERSION = "1.08";
RT->AddStyleSheets('calendar.css');
RT->AddJavaScript('calendar.js');
commit 8be04f9d7031b48c62ffca2ae9aaecd99accbae1
Author: Jason Crome <jcrome at bestpractical.com>
Date: Wed Sep 27 12:58:54 2023 -0400
Remove remaining RT4 support
diff --git a/etc/tabs_privileged_callback.patch b/etc/tabs_privileged_callback.patch
deleted file mode 100644
index 493c0f8..0000000
--- a/etc/tabs_privileged_callback.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
-index c6c6505..d4b2e59 100644
---- a/share/html/Elements/Tabs
-+++ b/share/html/Elements/Tabs
-@@ -757,6 +757,9 @@ my $build_main_nav = sub {
- }
- }
-
-+ # Scope here so we can share in the Privileged callback
-+ my $args = '';
-+ my $has_query = '';
- if (
- (
- $request_path =~ m{^/(?:Ticket|Search)/}
-@@ -767,8 +770,6 @@ my $build_main_nav = sub {
- )
- {
- my $search = Menu()->child('search')->child('tickets');
-- my $args = '';
-- my $has_query = '';
- my $current_search = $session{"CurrentSearchHash"} || {};
- my $search_id = $DECODED_ARGS->{'SavedSearchLoad'} || $DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || '';
- my $chart_id = $DECODED_ARGS->{'SavedChartSearchId'} || $current_search->{SavedChartSearchId};
-@@ -936,7 +937,7 @@ my $build_main_nav = sub {
- PageMenu()->child( edit => title => loc('Edit'), path => '/Prefs/MyRT.html' );
- }
-
-- $m->callback( CallbackName => 'Privileged', Path => $request_path );
-+ $m->callback( CallbackName => 'Privileged', Path => $request_path, Search_Args => $args, Has_Query => $has_query );
- };
-
- my $build_selfservice_nav = sub {
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 79fc444..0ac7da8 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -369,10 +369,6 @@ May need root permissions
=item patch RT
-Apply for versions prior to 4.4.2:
-
- patch -p1 -d /path/to/rt < etc/tabs_privileged_callback.patch
-
=item Edit your F</opt/rt5/etc/RT_SiteConfig.pm>
Add this line:
commit b587db7493eb30295a60c1a1ab5c5ac49f66cb6f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Tue Sep 26 21:38:59 2023 -0300
Fix filter on status bad behavior when changing months
When users changed the month on the calendar with the filter on status
enabled, and on the next month they changed the status filter, the
filter pre selecting the statuses from the args on the URI, and not from
the posted form.
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index b2bafe8..b87cd78 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -205,7 +205,16 @@ my $NewQuery = $DECODED_ARGS->{NewQuery};
my $BaseQuery = $DECODED_ARGS->{BaseQuery};
my $FilterOnStatusClear = $DECODED_ARGS->{FilterOnStatusClear};
my @FilterOnStatus;
-if ( $DECODED_ARGS->{FilterOnStatus} ) {
+if ( $DECODED_ARGS->{NewFilterOnStatus} ) {
+ if ( ref $DECODED_ARGS->{NewFilterOnStatus} eq 'ARRAY' ) {
+ @FilterOnStatus = @{$DECODED_ARGS->{NewFilterOnStatus}};
+ }
+ else {
+ push @FilterOnStatus, $DECODED_ARGS->{NewFilterOnStatus};
+ }
+}
+# This comes from the month changing form and link
+elsif ( $DECODED_ARGS->{FilterOnStatus} ) {
if ( ref $DECODED_ARGS->{FilterOnStatus} eq 'ARRAY' ) {
@FilterOnStatus = @{$DECODED_ARGS->{FilterOnStatus}};
}
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index 34ceaf0..b1f96ce 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -12,7 +12,7 @@
<form id="FilterOnStatusForm"
method="post">
- <select name="FilterOnStatus" id="FilterOnStatus"
+ <select name="NewFilterOnStatus" id="NewFilterOnStatus"
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 %>"
commit 7f227f40213daaf375d5ad15437a67bafb99d0d0
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Tue Sep 26 21:21:31 2023 -0300
Fix filter on status to not replace the default query
Filter on Status was replacing the default query of the Calendar
element, causing the calendar to show wrong events when selecting a
status that was not part of the default query.
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index 0042415..b2bafe8 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -249,12 +249,6 @@ my $set = DateTime::Set->from_recurrence(
next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
);
-if (@FilterOnStatus) {
- my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
- $Query .= " AND " if $Query;
- $Query .= "($StatusClause)";
-}
-
# Default Query and Format
my $TempFormat = "__Starts__ __Due__";
my $TempQuery = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
@@ -310,6 +304,12 @@ my %DateTypes = map { $_ => 1 } @Dates;
$TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->strftime("%F"));
+if (@FilterOnStatus) {
+ my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
+ $TempQuery .= " AND " if $TempQuery;
+ $TempQuery .= "($StatusClause)";
+}
+
$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
commit a2e43afe42190b9cdef7f54b8b6d6dc41c836719
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue Sep 26 11:47:07 2023 -0400
Handle Date custom fields correctly
Unlike DateTime, Date values don't have time part and thus no timezone.
E.g. for 2023-09-26, it should always be 2023-09-26, no matter what
timezone current user is in.
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index db29d65..5f018b3 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -92,8 +92,8 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
if (grep { $_ eq $cf_obj->Type} qw(DateTime Date)) {
my $date_value = RT::Date->new($session{'CurrentUser'});
my $date_format = $cf_obj->Type eq 'DateTime' ? 'ISO' : 'unknown';
- $date_value->Set(Format => $date_format, Value => $value, Timezone => 'UTC');
- $value = $date_value->AsString( Timezone => 'user' );
+ $date_value->Set(Format => $date_format, Value => $value);
+ $value = $date_value->AsString( Timezone => 'user', Time => $cf_obj->Type eq 'DateTime' ? 1 : 0 );
}
} else {
my $method = '$Object->'.$attr.'()';
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 6af5495..79fc444 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -199,7 +199,6 @@ sub GetDate {
$DateObj->Set(
Format => 'unknown',
Value => $CFDateValue,
- Timezone => 'utc'
);
} else {
$DateObj->Set( Format => 'ISO', Value => $CFDateValue );
commit 733bdbfcead93b4ca4158cd5af35d834882e647f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 22 16:42:24 2023 -0300
Fix event popup to appear correctly on the right side of the screen
The event popup was disappearing off the right side of the screen when
the event was near the right edge of the calendar. This commit fixes
that by making the popup appear on the left side of the event when the
event is near the right edge of the calendar.
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index f6568f3..0042415 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -85,6 +85,7 @@ while ($date <= $end) {
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);
+ push @classes, "weekday-$day_of_week";
for my $t ( RTx::Calendar::SortCalendarEvents(
\@{ $Tickets->{ $date->strftime("%F") } || [] },
@@ -117,7 +118,7 @@ while ($date <= $end) {
}
</%perl>
- <td class="<% @classes %>"><div class="inside-day">
+ <td class="<% join(' ', @classes) %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
% for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
% if ( grep { $_->id eq $week_ticket_position{$index}{id} }
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index 5bff533..da45a4e 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -23,6 +23,7 @@ while ($date <= $end) {
my @classes = ();
push @classes, "today" if (DateTime->compare($today, $date) == 0);
push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+ push @classes, "weekday-$day_of_week";
for my $t ( RTx::Calendar::SortCalendarEvents(
\@{ $Tickets->{ $date->strftime("%F") } || [] },
$sorting_field,
@@ -54,7 +55,7 @@ while ($date <= $end) {
}
</%perl>
- <td class="<% @classes %>"><div class="inside-day">
+ <td class="<% join(' ', @classes) %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
% for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
% if ( grep { $_->id eq $week_ticket_position{$index}{id} }
diff --git a/static/css/calendar.css b/static/css/calendar.css
index c6cb8c8..915a0b0 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -33,6 +33,12 @@ table.rtxcalendar div.day div.event-info:hover span.tip{
color: #505050;
}
+table.rtxcalendar td.weekday-7 div.day div.event-info:hover span.tip,
+table.rtxcalendar td.weekday-6 div.day div.event-info:hover span.tip,
+table.rtxcalendar td.weekday-5 div.day div.event-info:hover span.tip {
+ left: unset;
+ right: 24px;
+}
/* For the full calendar */
table.rtxcalendar {
commit 165c2735c5bc5375e73217c0e890be405035385b
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 22 16:10:07 2023 -0300
Remove the daily events sorting sub routine from the settings
The CalendarSortingEvents setting was introduced to allow the user to
sort the daily events in the calendar. That was first implemented for
giving a possibility sort events by status, grouping the events that are
in the same status together. Although that could be useful in a few
cases, it was a blocker to implement the multiple days events feature.
Since this feature was never introduced in a final version of the
extension, it is safe to remove it and sort the events by date.
Now the events are sorted by the first date field of the search format.
In order to make this sorting, we are using the GetDate method that was
previously a private method of the RTx::Calendar package. So we renamed
it from _GetDate to GetDate and made it public.
diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 6340a1f..5241e22 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -18,12 +18,6 @@ Set(%CalendarStatusColorMap, (
'stalled' => '#FF0000',
));
-Set($CalendarSortEvents, sub {
- my @Tickets = @_;
- my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
- return @SortedTickets;
-});
-
Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
Set(@CalendarFilterDefaultStatuses, qw(new open));
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index c621d40..f6568f3 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -86,14 +86,17 @@ while ($date <= $end) {
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") } || [] } )) {
+ for my $t ( RTx::Calendar::SortCalendarEvents(
+ \@{ $Tickets->{ $date->strftime("%F") } || [] },
+ $sorting_field,
+ $session{CurrentUser} ) ) {
# 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 ) {
+ for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
if ( $week_ticket_position{$index}{id} eq "" ) {
$free_index = $i;
last;
@@ -116,7 +119,7 @@ while ($date <= $end) {
<td class="<% @classes %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
-% for my $index ( sort keys %week_ticket_position ) {
+% for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
% if ( grep { $_->id eq $week_ticket_position{$index}{id} }
% @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
% my $t = $week_ticket_position{$index}{TicketObj};
@@ -319,5 +322,5 @@ my $DownloadQueryString =
OrderBy => $OrderBy,
);
-my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
+my $sorting_field = $Dates[0] || '';
</%INIT>
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index fb2e227..5bff533 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -23,14 +23,17 @@ 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") } || [] } )) {
+ for my $t ( RTx::Calendar::SortCalendarEvents(
+ \@{ $Tickets->{ $date->strftime("%F") } || [] },
+ $sorting_field,
+ $session{CurrentUser} ) ) {
# 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 ) {
+ for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
if ( $week_ticket_position{$index}{id} eq "" ) {
$free_index = $i;
last;
@@ -53,7 +56,7 @@ while ($date <= $end) {
<td class="<% @classes %>"><div class="inside-day">
<div class="calendardate"><%$date->day%></div>
-% for my $index ( sort keys %week_ticket_position ) {
+% for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
% if ( grep { $_->id eq $week_ticket_position{$index}{id} }
% @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
% my $t = $week_ticket_position{$index}{TicketObj};
@@ -143,5 +146,5 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
-my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
+my $sorting_field = $Dates[0] || '';
</%INIT>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 185cc43..6af5495 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -106,7 +106,7 @@ sub FindTickets {
# $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 = _GetDate( $Date, $Ticket, $CurrentUser );
+ my $dateindex = GetDate( $Date, $Ticket, $CurrentUser );
push @{ $Tickets{$dateindex } },
$Ticket
@@ -126,8 +126,8 @@ sub FindTickets {
grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
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 );
+ 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
@@ -173,11 +173,16 @@ sub FindTickets {
}
}
-sub _GetDate {
+sub GetDate {
my $date_field = shift;
my $Ticket = shift;
my $CurrentUser = shift;
+ unless ($date_field) {
+ $RT::Logger->debug("No date field provided. Using created date.");
+ $date_field = 'Created';
+ }
+
if ($date_field =~ /^CF\./){
my $cf = $date_field;
$cf =~ s/^CF\.\{(.*)\}/$1/;
@@ -206,6 +211,19 @@ sub _GetDate {
}
}
+sub SortCalendarEvents {
+ my $tickets_of_the_day = shift;
+ my $sorting_field = shift;
+ my $current_user = shift;
+ my @sorted_tickets = sort {
+ my ($a_value, $b_value);
+ $a_value = RTx::Calendar::GetDate( $sorting_field, $a, $current_user ) . " " . $a->id;
+ $b_value = RTx::Calendar::GetDate( $sorting_field, $b, $current_user ) . " " . $b->id;
+ ($a_value cmp $b_value)
+ } @$tickets_of_the_day;
+ return @sorted_tickets;
+}
+
#
# Take a user object and return the search with Description "calendar" if it exists
#
@@ -414,22 +432,6 @@ mouse over a date in F<etc/RT_SiteConfig.pm>:
'DueObj->ISO',
'CustomField.{Maintenance Estimated Start Date/Time - ET}'));
-=head3 Event sorting
-
-You can set the order that the events will presented in the day cell with
-the C<$CalendarSortEvents> setting.
-
-This setting takes a subroutine reference that will receive an array of
-L<RT::Ticket> objects and should return a sorted array of L<RT::Ticket>.
-
-The following example sorts the events by status:
-
- Set($CalendarSortEvents, sub {
- my @Tickets = @_;
- my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
- return @SortedTickets;
- });
-
=head3 Event colors
It's also possible to change the color of the events in the calendar by
commit ece6ba01afd5bbbb234fc112f771eac6156ce09c
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Wed Sep 20 08:23:39 2023 -0300
Remove extra line break of Event Types legend
There was a unnecessary line break on Types legend taking
extra space of the legend and sidebar. This patch removes
it.
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index 1619fca..34ceaf0 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -43,7 +43,7 @@
<span class="tiplegend">
<% $TranslatedLegend %>
</span>
- </span><br />
+ </span>
% }
</&>
commit 1e47535adc4fd0f2e3b02c8062bbf7209115af0a
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 15 16:14:06 2023 -0300
Update POD with CalendarFilterDefaultStatuses config information
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 0421abe..185cc43 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -454,6 +454,12 @@ F<etc/RT_SiteConfig.pm>:
Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+=head3 Default selected status on Filtering on Status field
+
+You can change the default selected statuses by adding them to the
+C<@CalendarFilterDefaultStatuses> setting to your F<etc/RT_SiteConfig.pm>:
+
+ Set(@CalendarFilterDefaultStatuses, qw(new open));
=head3 Custom icons
commit 9ef3675d0774da334e2cd0c71cc663c677abd0d1
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 15 16:09:23 2023 -0300
Add option to pre-select Filter on Status values
Add CalendarFilterDefaultStatuses option which allows to pre-select
Status values in the Filter on Status box for the first time the
calendar is displayed.
diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index d042d47..6340a1f 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -26,4 +26,6 @@ Set($CalendarSortEvents, sub {
Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+Set(@CalendarFilterDefaultStatuses, qw(new open));
+
1;
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index c5b1fc9..c621d40 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -189,6 +189,7 @@ while ($date <= $end) {
</&>
</div>
<%INIT>
+my $NotFirstAccess = $DECODED_ARGS->{NotFirstAccess};
my $Month = $DECODED_ARGS->{Month} || (localtime)[4];
my $Year = $DECODED_ARGS->{Year} || (localtime)[5] + 1900;
my $Query = $DECODED_ARGS->{Query};
@@ -207,7 +208,11 @@ if ( $DECODED_ARGS->{FilterOnStatus} ) {
else {
push @FilterOnStatus, $DECODED_ARGS->{FilterOnStatus};
}
+} else {
+ @FilterOnStatus = @{RT->Config->Get('CalendarFilterDefaultStatuses')}
+ unless $NotFirstAccess;
}
+$NotFirstAccess = 1;
if ($FilterOnStatusClear) {
$Query = $BaseQuery if $BaseQuery;
@@ -270,11 +275,12 @@ my $QueryString =
Format => $Format,
Order => $Order,
OrderBy => $OrderBy,
- Rows => $RowsPerPage
+ Rows => $RowsPerPage,
+ NotFirstAccess => $NotFirstAccess,
)
if ($Query);
-$QueryString ||= 'NewQuery=1';
+$QueryString ||= 'NewQuery=1&NotFirstAccess=1';
# we search all date types in Format string
my @CoreDates = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
commit 8d327b01bc81cb30c8e76c29e2bc2fc0381f4604
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 15 15:45:04 2023 -0300
Update calendar POD mentioning the new Portlets
Add Calendar and CalendarWithSidebar portlets to the POD.
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 36d5ee4..0421abe 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -372,12 +372,26 @@ Add this line:
=head1 CONFIGURATION
-=head2 Base configuration
+=head2 Use the calendar on Dashboard
+
+The calendar comes with 3 different portlets that can be added to your
+RT dashboards:
+
+=over
+
+=item C<MyCalendar>, a summary of the events for the current week.
+
+=item C<Calendar>, a full month of the calendar view, without sidebar.
+
+=item C<CalendarWithSidebar>, a full month of the calendar view, with
+sidebar which includes an extra status filter and legends of the calendars.
+
+=back
-To use the C<MyCalendar> portlet, you must add C<MyCalendar> to
C<$HomepageComponents> in F<etc/RT_SiteConfig.pm>:
- Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
+ Set($HomepageComponents, [qw(QuickCreate Quicksearch
+ MyCalendar Calendar CalendarWithSidebar
MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
=head2 Display configuration
commit aeb3159b2c1b54750568c341826bb8c503291d30
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Fri Sep 15 15:30:04 2023 -0300
Move calendar to Element file
Calendar can now be used as a dashboard portlet. Created two flavors of
the Element to be added to the dashboard. One with the sidebar and one
without.
diff --git a/html/Search/Calendar.html b/html/Elements/Calendar
similarity index 85%
copy from html/Search/Calendar.html
copy to html/Elements/Calendar
index 01ef818..c5b1fc9 100644
--- a/html/Search/Calendar.html
+++ b/html/Elements/Calendar
@@ -1,27 +1,10 @@
<%args>
-$Month => (localtime)[4]
-$Year => (localtime)[5] + 1900
-$Query => undef
-$Format => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-$NewQuery => 0
- at FilterOnStatus => undef
-$BaseQuery => undef
-$FilterOnStatusClear => undef
+$ShowSidebar => 0
</%args>
-<& /Elements/Header, Title => $title &>
-
-% if ( $m->comp_exists( '/Ticket/Elements/Tabs' ) ) {
-<& /Ticket/Elements/Tabs,
- current_tab => "Search/Calendar.html?$QueryString",
- Title => $title &>
-% } else {
- <& /Elements/Tabs &>
-% }
+<div class="calendar-container">
+% if ($ShowSidebar) {
<& /Elements/CalendarSidebar,
BaseQuery => $BaseQuery,
Month => $Month,
@@ -33,10 +16,12 @@ $FilterOnStatusClear => undef
FilterOnStatus => \@FilterOnStatus,
Dates => \@Dates,
&>
+% }
<div class="calendar-content">
<&| /Widgets/TitleBox,
title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year),
+ title_href => "Search/Calendar.html",
titleright => loc('Download Spreadsheet'),
titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
&>
@@ -49,7 +34,7 @@ $FilterOnStatusClear => undef
% $PYear--;
% $PMonth = 11;
% }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
+<a href="?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
</td>
<th align="center">
<font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
@@ -60,7 +45,7 @@ $FilterOnStatusClear => undef
% $NYear++;
% $NMonth = 0;
% }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
+<a href="?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
</td>
</tr>
</table>
@@ -170,11 +155,11 @@ while ($date <= $end) {
<table width="100%">
<tr>
<td align="left">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
+<a href="?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
</td>
<td valign="top" align="center">
-<form action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+<form method="post" class="changeCalendarMonth" onsubmit="changeCalendarMonth()">
<select name="Month" class="selectpicker">
% for (0..11) {
@@ -187,22 +172,43 @@ while ($date <= $end) {
<option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
% }
</select>
-
+<input type="hidden" id="querystring" value="<% $QueryString|n %>" />
<input type="submit" value="<% loc('Submit') %>" class="button btn btn-primary" />
</form>
</td>
<td align="right">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
+<a href="?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
</td>
</tr>
</table>
+</div>
+
</&>
- <& /Elements/CalendarFooter &>
</div>
<%INIT>
+my $Month = $DECODED_ARGS->{Month} || (localtime)[4];
+my $Year = $DECODED_ARGS->{Year} || (localtime)[5] + 1900;
+my $Query = $DECODED_ARGS->{Query};
+my $Format = $DECODED_ARGS->{Format};
+my $Order = $DECODED_ARGS->{Order};
+my $OrderBy = $DECODED_ARGS->{OrderBy};
+my $RowsPerPage = $DECODED_ARGS->{RowsPerPage};
+my $NewQuery = $DECODED_ARGS->{NewQuery};
+my $BaseQuery = $DECODED_ARGS->{BaseQuery};
+my $FilterOnStatusClear = $DECODED_ARGS->{FilterOnStatusClear};
+my @FilterOnStatus;
+if ( $DECODED_ARGS->{FilterOnStatus} ) {
+ if ( ref $DECODED_ARGS->{FilterOnStatus} eq 'ARRAY' ) {
+ @FilterOnStatus = @{$DECODED_ARGS->{FilterOnStatus}};
+ }
+ else {
+ push @FilterOnStatus, $DECODED_ARGS->{FilterOnStatus};
+ }
+}
+
if ($FilterOnStatusClear) {
$Query = $BaseQuery if $BaseQuery;
@FilterOnStatus = ();
@@ -234,7 +240,7 @@ my $set = DateTime::Set->from_recurrence(
next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
);
-if ($FilterOnStatus[0]) {
+if (@FilterOnStatus) {
my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
$Query .= " AND " if $Query;
$Query .= "($StatusClause)";
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index c752ae8..1619fca 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -1,11 +1,4 @@
<%args>
-$BaseQuery => undef
-$Month => undef
-$Year => undef
-$Format => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
@FilterOnStatus => undef
@Dates => undef
</%args>
@@ -18,14 +11,7 @@ $RowsPerPage => undef
&>
<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%>" />
+ method="post">
<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')}) {
diff --git a/html/Elements/CalendarWithSidebar b/html/Elements/CalendarWithSidebar
new file mode 100644
index 0000000..7cd30c5
--- /dev/null
+++ b/html/Elements/CalendarWithSidebar
@@ -0,0 +1,3 @@
+<& /Elements/Calendar,
+ ShowSidebar => 1,
+&>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 01ef818..c12b25d 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -1,311 +1,6 @@
-<%args>
-$Month => (localtime)[4]
-$Year => (localtime)[5] + 1900
-$Query => undef
-$Format => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-$NewQuery => 0
- at FilterOnStatus => undef
-$BaseQuery => undef
-$FilterOnStatusClear => undef
-</%args>
-
-<& /Elements/Header, Title => $title &>
-
-% if ( $m->comp_exists( '/Ticket/Elements/Tabs' ) ) {
-<& /Ticket/Elements/Tabs,
- current_tab => "Search/Calendar.html?$QueryString",
- Title => $title &>
-% } else {
- <& /Elements/Tabs &>
-% }
-
- <& /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,
- title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year),
- titleright => loc('Download Spreadsheet'),
- titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
- &>
-
-<table width="100%">
-<tr>
-<td align="left">
-% my ($PMonth, $PYear) = ($Month - 1, $Year);
-% if ($PMonth < 0) {
-% $PYear--;
-% $PMonth = 11;
-% }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
-</td>
-<th align="center">
- <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
-</th>
-<td align="right">
-% my ($NMonth, $NYear) = ($Month + 1, $Year);
-% if ($NMonth > 11) {
-% $NYear++;
-% $NMonth = 0;
-% }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
-</td>
-</tr>
-</table>
-
-<table class="rtxcalendar">
-
-<thead>
-<tr>
-% for ( @{$week{$weekstart}} ) {
-<th width="14%"><%$rtdate->GetWeekday($_)%></th>
-% }
-</tr>
-</thead>
-
-<tbody>
-<tr>
-<%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 $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>
-</table>
-
-<table width="100%">
-<tr>
-<td align="left">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
-</td>
-
-<td valign="top" align="center">
-<form action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
-
-<select name="Month" class="selectpicker">
-% for (0..11) {
-<option value="<%$_%>" <% $_ == $Month ? 'selected' : ''%> ><%$rtdate->GetMonth($_)%></option>
-% }
-</select>
-% my $year = (localtime)[5] + 1900;
-<select name="Year" class="selectpicker">
-% for ( ($year-5) .. ($year+5)) {
-<option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
-% }
-</select>
-
-<input type="submit" value="<% loc('Submit') %>" class="button btn btn-primary" />
-
-</form>
-</td>
-
-<td align="right">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
-</td>
-</tr>
-</table>
-
-</&>
- <& /Elements/CalendarFooter &>
-</div>
-<%INIT>
-if ($FilterOnStatusClear) {
- $Query = $BaseQuery if $BaseQuery;
- @FilterOnStatus = ();
-}
-$BaseQuery ||= $Query;
-my $title = loc("Calendar");
-
-my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
-
-my $rtdate = RT::Date->new($session{'CurrentUser'});
-
-my $weekstart = 'Sunday'; #RT::SiteConfig? user pref?
-my %week = (
- 'Saturday' => [6,0..5],
- 'Sunday' => [0..6],
- 'Monday' => [1..6,0],
-);
-my $startday_of_week = ${$week{$weekstart}}[0] || 7;
-my $endday_of_week = ${$week{$weekstart}}[-1] || 7;
-
-my $today = DateTime->today;
-my $yesterday = $today->clone->subtract( days=>1 );
-my $aweekago = $today->clone->subtract( days=>7 );
-my $date = RTx::Calendar::FirstDay($Year, $Month + 1, $startday_of_week );
-my $end = RTx::Calendar::LastDay ($Year, $Month + 1, $endday_of_week );
-
-# use this to loop over days until $end
-my $set = DateTime::Set->from_recurrence(
- next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
-);
-
-if ($FilterOnStatus[0]) {
- my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
- $Query .= " AND " if $Query;
- $Query .= "($StatusClause)";
-}
-
-# Default Query and Format
-my $TempFormat = "__Starts__ __Due__";
-my $TempQuery = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
- AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody' )
- AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
-
-if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) {
- $TempFormat = $Search->SubValue('Format');
- $TempQuery = $Search->SubValue('Query');
-}
-
-# we overide them if needed
-$TempQuery = $Query if $Query;
-$TempFormat = $Format if $Format;
-$Format = $TempFormat unless $Format;
-
-my $QueryString =
- $m->comp(
- '/Elements/QueryString',
- Query => $BaseQuery,
- FilterOnStatus => \@FilterOnStatus,
- Format => $Format,
- Order => $Order,
- OrderBy => $OrderBy,
- Rows => $RowsPerPage
- )
- if ($Query);
-
-$QueryString ||= 'NewQuery=1';
-
-# we search all date types in Format string
-my @CoreDates = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
-my @CustomFields = ( $TempFormat =~ /__(CustomField\.\{.*\})__/g );
-my @DateCustomFields;
-
-for my $CustomField (@CustomFields) {
- my $LintCustomField = $CustomField;
- $LintCustomField =~ s/CustomField\.\{(.*)\}/$1/;
- my $CustomFieldObj = RT::CustomField->new( RT->SystemUser );
- $CustomFieldObj->LoadByName( Name => $LintCustomField );
- push @DateCustomFields, $CustomField
- if $CustomFieldObj->id
- && ( $CustomFieldObj->Type eq 'Date'
- || $CustomFieldObj->Type eq 'DateTime' );
-}
-
-my @Dates = (@CoreDates, @DateCustomFields);
- at Dates = map { $_ =~ s/^CustomField\.(.*)$/CF.$1/; $_ } @Dates;
-
-# used to display or not a date in Element/CalendarEvent
-my %DateTypes = map { $_ => 1 } @Dates;
-
-$TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->strftime("%F"));
-
-$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
-
-my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
-
-my $DownloadQueryString =
- $m->comp(
- '/Elements/QueryString',
- Query => $TempQuery,
- Format => $Format,
- Order => $Order,
- OrderBy => $OrderBy,
- );
-
-my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
-</%INIT>
+<& /Elements/Header, Title => loc("Calendar") &>
+<& /Elements/Tabs &>
+<& /Elements/Calendar,
+ ShowSidebar => 1,
+&>
+<& /Elements/CalendarFooter &>
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 37858ac..c6cb8c8 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -129,6 +129,7 @@ a.calendar-toggle-sidebar.sidebar-off::before {
.calendar-sidebar-toggle-content {
float: left;
width: 230px;
+ margin-top: 20px;
}
.calendar-sidebar-toggle-content.sidebar-off {
width:unset;
@@ -170,3 +171,6 @@ table.rtxcalendar .day.last-day {
table.rtxcalendar .day.first-day.last-day {
border-radius: 5px;
}
+.calendar-container {
+ display: flow-root;
+}
diff --git a/static/js/calendar.js b/static/js/calendar.js
index a9a00ef..07a3958 100644
--- a/static/js/calendar.js
+++ b/static/js/calendar.js
@@ -1,6 +1,12 @@
window.onresize = resizeCalendarEventTitles;
jQuery(function() {
resizeCalendarEventTitles();
+
+ jQuery('.changeCalendarMonth').submit(function(e){
+ e.preventDefault();
+ changeCalendarMonth();
+ });
+
});
/*
@@ -39,3 +45,10 @@ function resizeCalendarEventTitles() {
}
)
}
+
+function changeCalendarMonth() {
+ var month = jQuery('.changeCalendarMonth select[name="Month"]').val();
+ var year = jQuery('.changeCalendarMonth select[name="Year"]').val();
+ var querystring = jQuery('.changeCalendarMonth #querystring').val();
+ window.location.href = "?Month=" + month + "&Year=" + year + "&" + querystring;
+}
commit 682f759318db8c61d142f52c828500b60b3fa75f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date: Wed Sep 13 22:38:25 2023 -0300
Upgrade date objects ad methods to RT::Date
Many of the date calculations of the calendar were done using different
perl date and time modules. This was causing issues to present events in
the user timezone.
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index c2f35cb..db29d65 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -9,8 +9,10 @@ $CurrentPostion => undef
</%args>
<%perl>
-my $icon = RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder);
-my $spanning_tickets_for_today = $TicketsSpanningDays->{$today} || [];
+my $icon
+= RTx::Calendar::GetEventImg( $Object, $today, $DateTypes, $IsReminder,
+ $session{'CurrentUser'} );
+my $spanning_tickets_for_today = $TicketsSpanningDays->{$today} || [];
my $spanning_tickets_for_tomorrow = $TicketsSpanningDays->{$tomorrow} || [];
my $first_day_of_the_event = 0;
my $last_day_of_the_event = 0;
@@ -19,7 +21,7 @@ my $last_day_of_the_event = 0;
if ( ( ! grep { $_ eq $TicketId } @$spanning_tickets_for_today ) ) {
$first_day_of_the_event = 1;
}
-if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
+if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
$last_day_of_the_event = 1;
# This frees up the position for the next ticket
$WeekTicketPosition->{$CurrentPostion}->{id} = "";
@@ -72,21 +74,34 @@ if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
:</strong> <% $subject%><br />
<br />
-%# logic taken from Ticket/Search/Results.tsv
-% foreach my $attr (@display_fields) {
-% my $value;
-%
-% if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
-% $value = '-';
-% } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
-% my $cf = $1;
-% $value = $Object->FirstCustomFieldValue($cf);
-% } else {
-% my $method = '$Object->'.$attr.'()';
-% $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
-% $value = eval $method;
-% if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
-% }
+<%perl>
+# logic taken from Ticket/Search/Results.tsv
+ foreach my $attr (@display_fields) {
+ my $value;
+
+ if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
+ $value = '-';
+ } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
+ my $cf = $1;
+ my $cf_obj = $Object->LoadCustomFieldByIdentifier($cf);
+ unless ($cf_obj->id) {
+ $RT::Logger->debug("Custom field '$cf' not available for ticket #".$Object->id);
+ next;
+ }
+ $value = $Object->FirstCustomFieldValue($cf);
+ if (grep { $_ eq $cf_obj->Type} qw(DateTime Date)) {
+ my $date_value = RT::Date->new($session{'CurrentUser'});
+ my $date_format = $cf_obj->Type eq 'DateTime' ? 'ISO' : 'unknown';
+ $date_value->Set(Format => $date_format, Value => $value, Timezone => 'UTC');
+ $value = $date_value->AsString( Timezone => 'user' );
+ }
+ } else {
+ my $method = '$Object->'.$attr.'()';
+ $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
+ $value = eval $method;
+ if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
+ }
+</%perl>
<strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
% }
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index c2882ab..36d5ee4 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -34,14 +34,6 @@ sub LastDay {
$day;
}
-# we can't use RT::Date::Date because it uses gmtime
-# and we need localtime
-sub LocalDate {
- my $ts = shift;
- my ( $d, $m, $y ) = ( localtime($ts) )[ 3 .. 5 ];
- sprintf "%4d-%02d-%02d", ( $y + 1900 ), ++$m, $d;
-}
-
sub DatesClauses {
my ( $Dates, $begin, $end ) = @_;
@@ -143,19 +135,19 @@ sub FindTickets {
$current_date->Set(
Format => 'unknown',
Value => $starts_date,
- Timezone => 'utc'
+ Timezone => 'user'
);
my $end_date_unix = RT::Date->new($CurrentUser);
$end_date_unix->Set(
Format => 'unknown',
Value => $ends_date,
- Timezone => 'utc'
+ Timezone => 'user'
);
$end_date_unix = $end_date_unix->Unix;
my $first_day = 1;
while ( $current_date->Unix <= $end_date_unix )
{
- my $dateindex = LocalDate( $current_date->Unix );
+ my $dateindex = $current_date->ISO( Time => 0, Timezone => 'user' );
push @{ $TicketsSpanningDays{$dateindex} }, $Ticket->id
unless $first_day
@@ -207,10 +199,10 @@ sub _GetDate {
} else {
$DateObj->Set( Format => 'ISO', Value => $CFDateValue );
}
- return LocalDate( $DateObj->Unix );
+ return $DateObj->ISO( Time => 0, Timezone => 'user' );
} else {
my $DateObj = $date_field . "Obj";
- return LocalDate( $Ticket->$DateObj->Unix );
+ return $Ticket->$DateObj->ISO( Time => 0, Timezone => 'user' );
}
}
@@ -265,6 +257,7 @@ sub GetEventImg {
my $CurrentDate = shift;
my $DateTypes = shift;
my $IsReminder = shift;
+ my $CurrentUser = shift;
my $EventIcon;
my %CalendarIcons = RT->Config->Get('CalendarIcons');
CALENDAR_ICON:
@@ -279,7 +272,7 @@ CALENDAR_ICON:
$ComparedDate =~ s/^\s+|\s+$//g;
if ( $DateField eq 'Reminder' ) {
if ( $IsReminder
- && RTx::Calendar::LocalDate( $Object->DueObj->Unix ) eq
+ && $Object->DueObj->ISO( Time => 0, Timezone => 'user' ) eq
$CurrentDate )
{
$EventIcon = 'reminder.png';
@@ -290,12 +283,17 @@ CALENDAR_ICON:
$cf =~ s/^CF\.\{(.*)\}/$1/;
my $DateValue = $Object->FirstCustomFieldValue($cf);
next CALENDAR_ICON unless $DateValue;
- $DateValue =~ s/(.*) (.*)/$1/;
+ my $DateObj = RT::Date->new( $CurrentUser );
+ my $CustomFieldObj = RT::CustomField->new( $CurrentUser );
+ $CustomFieldObj->LoadByName( Name => $cf );
+ my $date_format = $CustomFieldObj->Type eq 'Date' ? 'unknown' : 'ISO';
+ $DateObj->Set( Format => $date_format, Value => $DateValue );
+ $DateValue = $DateObj->ISO( Time => 0, Timezone => 'user' );
next CALENDAR_ICON unless $DateValue eq $CurrentDate;
} else {
my $DateObj = $ComparedDate . "Obj";
my $DateValue
- = RTx::Calendar::LocalDate( $Object->$DateObj->Unix );
+ = $Object->$DateObj->ISO( Time => 0, Timezone => 'user' );
next CALENDAR_ICON unless $DateValue eq $CurrentDate;
}
commit 46a1cecb7005bf18be5b09cc4258e20640a82a4e
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 aa2650b47f8130a10e9b1ec03b3f746b8a903cba
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 33f8fccb079501908ef04655b6400b2c0b62a2e5
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 bd35c81fb9116349d59fa6173c1346ad9a5b5f09
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..c2f35cb 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -2,20 +2,70 @@
$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} || [];
+my $first_day_of_the_event = 0;
+my $last_day_of_the_event = 0;
+# If the ticket is not in the spanning tickets for today array, it means
+# it's the first day of the event
+if ( ( ! grep { $_ eq $TicketId } @$spanning_tickets_for_today ) ) {
+ $first_day_of_the_event = 1;
+}
+if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
+ $last_day_of_the_event = 1;
+ # This frees up the position for the next ticket
+ $WeekTicketPosition->{$CurrentPostion}->{id} = "";
+}
+</%perl>
+
+<div class="day
+% if ( $last_day_of_the_event || $DayOfWeek eq 7 ) {
+ last-day
+% }
+% if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
+ first-day
+% }
+" style="
% if ( $CalendarStatusColorMap{$status} ) {
- style="color: <%$CalendarStatusColorMap{$status}%>;"
+ background-color: <%$CalendarStatusColorMap{$status}%>;
+% }
+% # We need to decrease the z-index of the spanning days of an event
+% # so the event title (which is placed on the div of the first day of the
+% # event and has a z-index of 4) is visible, since it cross multiple days.
+% 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_the_event
+% && !$first_day_of_the_event) {
+ float: right;
+% }
+ ">
+ <% $icon|n %>
+ </div>
+ <div class="event-info">
+% if ( $first_day_of_the_event || $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 +90,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 e03f2b6..c2882ab 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 ) = @_;
@@ -151,12 +152,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
@@ -167,6 +170,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..a9a00ef
--- /dev/null
+++ b/static/js/calendar.js
@@ -0,0 +1,41 @@
+window.onresize = resizeCalendarEventTitles;
+jQuery(function() {
+ resizeCalendarEventTitles();
+});
+
+/*
+* Adjust the max-width of the event title according to the number spanning
+* days of an event for each week of the calendar (including MyCalendar
+* portlet) so it doesn't escape the event box.
+*/
+function resizeCalendarEventTitles() {
+ if (jQuery('.rtxcalendar').length == 0){
+ return;
+ }
+ var current_width = jQuery('.rtxcalendar')
+ .find('.inside-day').first().css('width').replace('px','');
+ 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
+ // we substract 22px to display the icon of the last day of the event
+ 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] * current_width)-22) + 'px');
+ })
+ }
+ }
+ )
+}
commit 638c33959a8fa1d7785ea3621d6aa72897d23457
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..e03f2b6 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -49,6 +49,30 @@ 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.
+ # Start and end fields of the multiple days must also be present on the
+ # format.
+ my $multiple_days_events = RT->Config->Get('CalendarMultipleDaysEvents');
+ for my $event ( keys %$multiple_days_events ) {
+ next unless
+ grep { $_ eq $multiple_days_events->{$event}{'Starts'} } @$Dates;
+ next unless
+ grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
+ 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 +82,19 @@ 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 ) {
+ next unless
+ grep { $_ eq $multiple_days_events->{$event}{'Starts'} } @$Dates;
+ next unless
+ grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
+ 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 +103,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 +124,90 @@ sub FindTickets {
or $AlreadySeen{ $dateindex }
{$Ticket}++;
}
+
+ # Find spanning days of multiple days events
+ for my $event (sort keys %$multiple_days_events) {
+ next unless
+ grep { $_ eq $multiple_days_events->{$event}{'Starts'} } @$Dates;
+ next unless
+ grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
+ 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 $CustomFieldObj = $Ticket->LoadCustomFieldByIdentifier($cf);
+ unless ($CustomFieldObj->id) {
+ RT->Logger->debug("$cf Custom Field is not available for this object.");
+ return;
+ }
+ my $CFDateValue = $Ticket->FirstCustomFieldValue($cf);
+ return unless $CFDateValue;
+ 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 +451,22 @@ 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',
+ },
+ )
+ );
+
+Note that the Starts and Ends fields must be included in the search result
+Format in order the event to be displayed on the calendar.
+
=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