[Bps-public-commit] rtx-calendar branch allow-custom-field-based-events created. 1.05-23-g94705d7

BPS Git Server git at git.bestpractical.com
Fri Aug 25 18:02:45 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, allow-custom-field-based-events has been created
        at  94705d7d923c31ea7444f9908b1ff02495953eaa (commit)

- Log -----------------------------------------------------------------
commit 94705d7d923c31ea7444f9908b1ff02495953eaa
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Aug 25 15:00:32 2023 -0300

    Move Status Filter to the bottom of the Calendar
    
    Restored the same code that was in the previous version using tables
    rather than divs with bootstrap classes, so we can keep compatibility
    with old versions of RT as well.
    
    Also make the Clear button always visible and rename it to Clear Filter.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 4b09fce..f4fac15 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -28,57 +28,29 @@ $FilterOnStatusClear => undef
      titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
      &>
 
-<form id="FilterOnStatusForm"
-  action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
-  <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
-  <div class="container col-12 mt-1 mb-1">
-    <div class="row align-items-center">
-      <div class="col-1 text-left">
+<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>
-      </div>
-      <div class="col text-center">
-        <div class="row">
-        <div class="col-2">
-          <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
-        </div>
-        <div class="col-2 label"><% loc( 'Filter on [_1]', loc('Status') ) %>:</div>
-      <div class="col-3">
-          <select name="FilterOnStatus" id="FilterOnStatus"
-            class="selectpicker form-control tall" 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>
-      <div class="col text-left">
-% if ($FilterOnStatus[0]) {
-        <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
-          value="1" class="button btn btn-primary"><% loc('Clear') %></button>
-% }
-        <input type="submit" value="<% loc('Filter') %>" class="button btn btn-primary" />
-      </div>
-    </div>
-    </div>
-    <div class="col-1 text-right">
+<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>
-    </div>
-  </div>
-</div>
-</form>
+<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
+</td>
+</tr>
+</table>
 
 <table class="rtxcalendar">
 
@@ -150,6 +122,31 @@ $FilterOnStatusClear => undef
 </tr>
 </table>
 
+<table width="100%">
+<tr>
+<td valign="top" align="center">
+  <form id="FilterOnStatusForm"
+  action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+  <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
+  <% loc( 'Filter on [_1]', loc('Status') ) %>:
+  <select name="FilterOnStatus" id="FilterOnStatus"
+    class="selectpicker" 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>
+  <input type="submit" value="<% loc('Filter') %>" class="button btn btn-primary" />
+  <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
+    value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
+</form>
+</td>
+</tr>
+</table>
+
 <div class="container mt-4">
   <div class="row">
     <div class="col-6">

commit 1cb5627aef8b8c4f4616f19b6b59c4b083bd9ee3
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Aug 25 14:19:01 2023 -0300

    Move Calendar Spreadsheet Download link
    
    Convert it to a link on the top right of the Calendar instead of a button.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 473d3e8..4b09fce 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -23,7 +23,10 @@ $FilterOnStatusClear => undef
 % }
 
 <&| /Widgets/TitleBox,
-     title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year) &>
+     title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year),
+     titleright => loc('Download Spreadsheet'),
+     titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
+     &>
 
 <form id="FilterOnStatusForm"
   action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
@@ -62,8 +65,6 @@ $FilterOnStatusClear => undef
           value="1" class="button btn btn-primary"><% loc('Clear') %></button>
 % }
         <input type="submit" value="<% loc('Filter') %>" class="button btn btn-primary" />
-        <a href="<%$RT::WebPath%>/Search/Results.tsv?<%$DownloadQueryString%>"
-          class="button btn btn-primary ml-2"><% loc('Download as Spreadsheet') %></a>
       </div>
     </div>
     </div>

commit 9b49caca6ce8e3f64ffcaf3f159f19494ae9b6dc
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Aug 25 13:51:37 2023 -0300

    Make the available statuses on the filter configurable
    
    Add the @CalendarFilterStatuses default setting.
    
    Also update add a description of that setting to the POD.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 73acc21..d042d47 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -24,4 +24,6 @@ Set($CalendarSortEvents, sub {
     return @SortedTickets;
 });
 
+Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+
 1;
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 26b9682..473d3e8 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -47,7 +47,7 @@ $FilterOnStatusClear => undef
       <div class="col-3">
           <select name="FilterOnStatus" id="FilterOnStatus"
             class="selectpicker form-control tall" multiple="multiple" size="6">
-% for my $Status (sort keys %StatusesAvailable) {
+% 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"':''%>
@@ -341,14 +341,5 @@ my $DownloadQueryString =
         OrderBy => $OrderBy,
       );
 
-my %StatusesAvailable;
-for my $Dates (keys %Tickets) {
-    my $TicketsOnTheDate = $Tickets{$Dates};
-    for my $Ticket (@$TicketsOnTheDate) {
-        my $Status = $Ticket->Status;
-        $StatusesAvailable{$Status}++;
-    }
-}
-
 my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
 </%INIT>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index c3dbff7..9a466a7 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -326,6 +326,15 @@ adding the C<$CalendarStatusColorMap> setting to your F<etc/RT_SiteConfig.pm>:
 You can use any color declaration that CSS supports, including hex codes,
 color names, and RGB values.
 
+=head3 Event filtering by status
+
+You can change the statuses available for filtering on the calendar by
+adding the C<@CalendarFilterStatuses> setting to your
+F<etc/RT_SiteConfig.pm>:
+
+    Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+
+
 =head3 Custom icons
 
 Custom Icons can be defined for the events in the calendar by adding the

commit e899da2b65861b28549683da165c1dc0b6213d7e
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Aug 25 11:40:01 2023 -0300

    Create a Default setting for CalendarSortEvents
    
    Remove unnecessary checks since we now have a default sub routine for
    the CalendarSortEvents config option.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index ad5d47f..73acc21 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -18,4 +18,10 @@ Set(%CalendarStatusColorMap, (
     'stalled'                               => '#FF0000',
 ));
 
+Set($CalendarSortEvents, sub {
+    my @Tickets = @_;
+    my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
+    return @SortedTickets;
+});
+
 1;
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 59406cd..26b9682 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -350,10 +350,5 @@ for my $Dates (keys %Tickets) {
     }
 }
 
-my $SortCalendarEvents = sub {
-  return @_;
-};
-if (RT->Config->Get("CalendarSortEvents")) {
-  $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
-}
+my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
 </%INIT>

commit 0aa12acec559221cf32a7679ed1feff847a95c59
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Aug 25 11:22:51 2023 -0300

    Add information about how add Custom Field dates to the Calendar
    
    Since we support Custom Field dates in the Calendar, we add a note
    about it in the Calendar Help section.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 4633bfb..59406cd 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -217,12 +217,14 @@ and add something to the Query like that:
 <&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
 By default RTx::Calendar display Due and Starts dates. You can select other
 kind of events you want with the Display Columns section in the [_1].
-The following one will display the two latter and LastUpdated dates:
+The following one will display the two latter, LastUpdated and an additional
+date from a custom field called Maintenance Date:
 </&>
 <pre>
   '<small>__Due__</small>',
   '<small>__Starts__</small>',
-  '<small>__LastUpdated__</small>'
+  '<small>__LastUpdated__</small>',
+  '<small>__CustomField.{Maintenance Date}__</small>'  
 </pre>
 </p>
 

commit bb3e52dadb8bcfa4008f799c99ebdd8a1a57ec30
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Aug 25 09:56:29 2023 -0300

    Refactor Event Color per Status feature
    
    Rename it from CalendarColorStatusMap to CalendarStatusColorMap.
    
    Create a default configuration for it in etc/RTxCalendar_Config.pm and
    refactor the code removing some unnecessary if statements.
    
    Move the event html style code to Mason template, where it's rendered
    for better readability of the code.
    
    Update POD with more info about the accepted colors on the configuration.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 35d9ccf..ad5d47f 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -10,4 +10,12 @@ Set(%CalendarIcons, (
     'LastUpdated'  => 'updated.png',
 ));
 
+Set(%CalendarStatusColorMap, (
+    'new'                                   => '#87873c',
+    'open'                                  => '#5555f8',
+    'rejected'                              => '#FF0000',
+    'resolved'                              => '#72b872',
+    'stalled'                               => '#FF0000',
+));
+
 1;
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index a125970..61a22a9 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -7,7 +7,11 @@ $DateTypes => undef
 <small>
 
     <% RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder)|n %>
-	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>" <% $StatusColor|n %>>
+	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"
+% if ( $CalendarStatusColorMap{$status} ) {
+    style="color: <%$CalendarStatusColorMap{$status}%>;"
+% }
+        >
            <% $Object->QueueObj->Name %> #<% $TicketId %>
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
            <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br />
@@ -66,15 +70,7 @@ if ($Object->Type eq 'reminder') {
     $status = $Object->Status;
 }
 
-# Colors per status
-my %CalendarColorStatusMap;
-if (RT->Config->Get('CalendarColorStatusMap')) {
-    %CalendarColorStatusMap = RT->Config->Get('CalendarColorStatusMap');
-}
-my $StatusColor = '';
-if ($CalendarColorStatusMap{$status}) {
-    $StatusColor ='style="color: '.$CalendarColorStatusMap{$status}.'"';
-}
+my %CalendarStatusColorMap = RT->Config->Get('CalendarStatusColorMap');
 
 my $display_owner = $RT::CalendarDisplayOwner;
 $display_owner ||= RT->Config->Get('CalendarDisplayOwner')
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index efe4a51..4633bfb 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -188,13 +188,11 @@ foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
 %     }
     </div>
     <div class="col-6">
-%     if (RT->Config->Get('CalendarColorStatusMap')) {
         <b><&|/l&>State Colors</&></b><br />
-%       my %ColorStatusMap = RT->Config->Get('CalendarColorStatusMap');
+%       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>
 </div>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 53d9348..c3dbff7 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -313,9 +313,9 @@ The following example sorts the events by status:
 =head3 Event colors
 
 It's also possible to change the color of the events in the calendar by
-adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
+adding the C<$CalendarStatusColorMap> setting to your F<etc/RT_SiteConfig.pm>:
 
-    Set(%CalendarColorStatusMap, (
+    Set(%CalendarStatusColorMap, (
         'new'                                   => 'blue',
         'open'                                  => 'blue',
         'approved'                              => 'green',
@@ -323,6 +323,9 @@ adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
         'resolved'                              => '#aaa',
     ));
 
+You can use any color declaration that CSS supports, including hex codes,
+color names, and RGB values.
+
 =head3 Custom icons
 
 Custom Icons can be defined for the events in the calendar by adding the

commit 1bc3d5cd10fb6a426af7afdd44bc8964d8550af6
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 19:31:57 2023 -0300

    Refactor Custom Icon feature
    
    Renamed the method from GetEventIcon to GetEventImg and sticked it to
    only allow image files and not html code.
    
    Also, since all the Icons can be customized, we created a default setting
    for all the core Dates icons.
    
    Also renamed the Icon setting from CalendarCustomIcons to CalendarIcons,
    since it's not only for custom icons anymore.
    
    Add an extra loop to the icon get feature to treat icons that are the
    reflection of combined dates.
    
    Update POD mentioning where to place the custom images.
    
    Remove unnecessary Legend variable.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
new file mode 100644
index 0000000..35d9ccf
--- /dev/null
+++ b/etc/RTxCalendar_Config.pm
@@ -0,0 +1,13 @@
+Set(%CalendarIcons, (
+    'Reminder'     => 'reminder.png',
+    'Resolved'     => 'resolved.png',
+    'Starts, Due'  => 'starts_due.png',
+    'Created, Due' => 'created_due.png',
+    'Created'      => 'created.png',
+    'Due'          => 'due.png',
+    'Starts'       => 'starts.png',
+    'Started'      => 'started.png',
+    'LastUpdated'  => 'updated.png',
+));
+
+1;
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index f684aa0..a125970 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -6,7 +6,7 @@ $DateTypes => undef
 <div class="day">
 <small>
 
-    <% RTx::Calendar::GetEventIcon($Object, $today, $DateTypes, $IsReminder)|n %>
+    <% RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder)|n %>
 	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>" <% $StatusColor|n %>>
            <% $Object->QueueObj->Name %> #<% $TicketId %>
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index fe64d11..efe4a51 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -156,34 +156,35 @@ $FilterOnStatusClear => undef
         <div class="col-2 pl-1 pr-1"> </div>
         <div class="col-auto text-left pl-1 pr-1"><b><&|/l&>Event Types</&></b></div>
       </div>
-%     foreach my $legend (sort keys %legend) {
-        <div class="row">
-          <div class="col-3 text-right pl-1 pr-1">
-            <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" />
-          </div>
-          <div class="col text-left pl-1 pr-1">
-%           my $more = 0;
-%           foreach ( @{$legend{$legend}} ) {
-              <% $more++ ? ', ' : '' %>
-              <&|/l&><% $_ %></&>
-%           }
-          </div>
-        </div>
-%     }
-%     if (RT->Config->Get('CalendarCustomIcons')) {
-%       my %CalendarCustomIcons = RT->Config->Get('CalendarCustomIcons');
-%       foreach my $legend (sort keys %CalendarCustomIcons) {
-%         my $legendLabel = $legend;
-%         $legendLabel =~ s/^CF\.\{(.*)\}/$1/;
+<%perl>
+my %CalendarIcons = RT->Config->Get('CalendarIcons');
+# Sort the legend after translation
+my %CalendarIconsTranslated;
+my $LegendLabel;
+foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
+  my @LegendLabels = split ',', $legend;
+  $LegendLabel = join ', ',
+    map {
+      $_ =~ s/^\s+|\s+$//g;
+      $_ =~ s/^CF\.\{(.*)\}/$1/;
+      $_ = 'Last Updated' if $_ eq 'LastUpdated';
+      loc($_)
+    } @LegendLabels;
+  $CalendarIconsTranslated{$LegendLabel} = $legend;
+}
+foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
+</%perl>
           <div class="row">
             <div class="col-3 text-right pl-1 pr-1">
-              <span class="tip"><% $CalendarCustomIcons{$legend}|n %></span>
+              <span class="tip">
+                <img
+                src="<% $RT::WebImagesURL %>/<%
+                $CalendarIcons{$CalendarIconsTranslated{$TranslatedLegend}}|n %>" /></span>
             </div>
             <div class="col text-left pl-1 pr-1">
-              <&|/l&><% $legendLabel %></&>
+              <% $TranslatedLegend %>
             </div>
           </div>
-%       }
 %     }
     </div>
     <div class="col-6">
@@ -237,20 +238,6 @@ portlet by saving a query with the name <code>calendar</code> in the [_1].
 
 </&>
 
-<%ONCE>
-
-my %legend = (
-  'created'     => ['Created'],
-  'due'         => ['Due'],
-  'resolved'    => ['Resolved'],
-  'updated'     => ['Last Updated'],
-  'created_due' => ['Created','Due'],
-  'reminder'    => ['Reminders'],
-  'started'     => ['Started'],
-  'starts_due'  => ['Starts','Due'],
-);
-
-</%ONCE>
 <%INIT>
 if ($FilterOnStatusClear) {
   $Query = $BaseQuery if $BaseQuery;
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 780e59f..53d9348 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -160,71 +160,57 @@ sub SearchDefaultCalendar {
     }
 }
 
-sub GetEventIcon {
-    my $Object   = shift;
+sub GetEventImg {
+    my $Object      = shift;
     my $CurrentDate = shift;
-    my $DateTypes = shift;
+    my $DateTypes   = shift;
     my $IsReminder  = shift;
     my $EventIcon;
-    if (RT->Config->Get('CalendarCustomIcons')) {
-        my %CalendarCustomIcons = RT->Config->Get('CalendarCustomIcons');
-        for my $DateField (keys %CalendarCustomIcons) {
-            my $DateValue;
-            if ($DateField =~ /^CF\./){
+    my %CalendarIcons = RT->Config->Get('CalendarIcons');
+CALENDAR_ICON:
+    for my $DateField ( keys %CalendarIcons ) {
+
+      # Icon can be a combination of two dates such as Due-Created,
+      # or CF.{Date Field}-Created. It can be also single date such as Created
+        my @DatesToCompare = split( /,/, $DateField );
+    DATE_COMPARE:
+        for my $ComparedDate (@DatesToCompare) {
+            # trim spaces
+            $ComparedDate =~ s/^\s+|\s+$//g;
+            if ( $DateField eq 'Reminder' ) {
+                if ( $IsReminder
+                    && RTx::Calendar::LocalDate( $Object->DueObj->Unix ) eq
+                    $CurrentDate )
+                {
+                    $EventIcon = 'reminder.png';
+                    last CALENDAR_ICON;
+                }
+            } elsif ( $DateField =~ /^CF\./ ) {
                 my $cf = $DateField;
                 $cf =~ s/^CF\.\{(.*)\}/$1/;
-                $DateValue = $Object->FirstCustomFieldValue($cf);
-                next unless $DateValue;
+                my $DateValue = $Object->FirstCustomFieldValue($cf);
+                next CALENDAR_ICON unless $DateValue;
                 $DateValue =~ s/(.*) (.*)/$1/;
+                next CALENDAR_ICON unless $DateValue eq $CurrentDate;
             } else {
-                my $DateObj = $DateField . "Obj";
-                $DateValue = LocalDate( $Object->$DateObj->Unix );
-            }
-            if ($DateValue eq $CurrentDate) {
-                $EventIcon = $CalendarCustomIcons{$DateField};
-                return $EventIcon;
+                my $DateObj = $ComparedDate . "Obj";
+                my $DateValue
+                    = RTx::Calendar::LocalDate( $Object->$DateObj->Unix );
+                next CALENDAR_ICON unless $DateValue eq $CurrentDate;
             }
+
+            # If we are here, it means that all comparissons are true
+            $EventIcon = $CalendarIcons{$DateField};
         }
     }
-    if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/reminder.png" />';
-
-    } elsif ($DateTypes->{Resolved}
-               and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $CurrentDate) {
-            $EventIcon = '<img src="' . $RT::WebImagesURL .  '/resolved.png" />';
-
-    } elsif ($DateTypes->{Starts} and $DateTypes->{Due}
-               and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $CurrentDate and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate ) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/starts_due.png" />';
-
-    } elsif ($DateTypes->{Due} and $DateTypes->{Created}
-               and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $CurrentDate ) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/created_due.png" />';
-
-    } elsif ($DateTypes->{Starts}
-               and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $CurrentDate) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/starts.png" />';
-
-    } elsif ($DateTypes->{Due}
-               and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/due.png" />';
-
-    } elsif ($DateTypes->{Created}
-               and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $CurrentDate) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/created.png" />';
-
-    } elsif ($DateTypes->{Started}
-               and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $CurrentDate) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/started.png" />';
-
-    } elsif ($DateTypes->{LastUpdated}
-               and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $CurrentDate) {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/updated.png" />';
-
+    if ($EventIcon) {
+        return '<img src="' . $RT::WebImagesURL . '/' . $EventIcon . '" />';
+    } else {
+        return '';
     }
-    return $EventIcon;
 }
 
+
 1;
 
 __END__
@@ -340,13 +326,15 @@ adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
 =head3 Custom icons
 
 Custom Icons can be defined for the events in the calendar by adding the
-C<$CalendarCustomIcons> setting to your F<etc/RT_SiteConfig.pm>:
+C<$CalendarIcons> setting to your F<etc/RT_SiteConfig.pm>:
 
-    Set(%CalendarCustomIcons, (
+    Set(%CalendarIcons, (
         'CF.{Maintenance Estimated Start Date/Time - ET}'
-            => '<small class="ml-1"><i class="fas fa-cog"></i></small>'
+            => 'maint.png',
     ));
 
+The images should be placed on F<local/static/images>.
+
 =head1 USAGE
 
 A small help section is available in /Search/Calendar.html

commit 72650586f5057a7da712c9c3324603d1036387e4
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 16:03:46 2023 -0300

    Update custom color feature to use reminder status
    
    It was coloring the event based on the associated ticket status, but
    the status of the reminder is more important.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 9a6750f..f684aa0 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -58,7 +58,7 @@ if ($Object->Type eq 'reminder') {
         $ticket   = $Object->RefersTo->First->TargetObj;
         $TicketId = $ticket->Id;
         $subject = $Object->Subject . " (" . $ticket->Subject . ")";
-        $status = $ticket->Status;
+        $status = $Object->Status;
     }
 } else {
     $TicketId = $Object->Id;

commit db7281db5587192bd92c0566daccba98e1badfd8
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 14:54:21 2023 -0300

    Update CalendarSortEvents setting documentation
    
    Describe that the setting is a subroutine reference, and what it
    expects and returns.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index faef4ac..780e59f 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -311,8 +311,12 @@ mouse over a date in F<etc/RT_SiteConfig.pm>:
 =head3 Event sorting
 
 You can set the order that the events will presented in the day cell with
-the C<$CalendarSortEvents> setting. The following example sorts
-the events by status:
+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 = @_;

commit 1525452251653329e8249b83ee2bca7105588c1f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 14:51:10 2023 -0300

    Update POD organization
    
    Add third level headings to organize display configurations and
    make it easier to describe each feature.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 17c154c..faef4ac 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -294,16 +294,22 @@ C<$HomepageComponents> in F<etc/RT_SiteConfig.pm>:
 
 =head2 Display configuration
 
+=head3 Displaying the owner
+
 You can show the owner in each day box by adding this line to your
 F<etc/RT_SiteConfig.pm>:
 
     Set($CalendarDisplayOwner, 1);
 
+=head3 Displaying custom fields
+
 You can change which fields show up in the popup display when you
 mouse over a date in F<etc/RT_SiteConfig.pm>:
 
     Set(@CalendarPopupFields, ('Status', 'OwnerObj->Name', 'DueObj->ISO'));
 
+=head3 Event sorting
+
 You can set the order that the events will presented in the day cell with
 the C<$CalendarSortEvents> setting. The following example sorts
 the events by status:
@@ -314,6 +320,8 @@ the events by status:
         return @SortedTickets;
     });
 
+=head3 Event colors
+
 It's also possible to change the color of the events in the calendar by
 adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
 
@@ -325,6 +333,8 @@ adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
         'resolved'                              => '#aaa',
     ));
 
+=head3 Custom icons
+
 Custom Icons can be defined for the events in the calendar by adding the
 C<$CalendarCustomIcons> setting to your F<etc/RT_SiteConfig.pm>:
 

commit 8c18c89aa77ae6cb939d0c9c5a2d31419d309bc6
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 14:46:17 2023 -0300

    Update Calendar sorting config name
    
    Update the config name from CalendarSortCalendarEvents to
    CalendarSortEvents.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index b037886..fe64d11 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -366,7 +366,7 @@ for my $Dates (keys %Tickets) {
 my $SortCalendarEvents = sub {
   return @_;
 };
-if (RT->Config->Get("CalendarSortCalendarEvents")) {
-  $SortCalendarEvents = RT->Config->Get("CalendarSortCalendarEvents");
+if (RT->Config->Get("CalendarSortEvents")) {
+  $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
 }
 </%INIT>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index dd3a6fd..17c154c 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -305,10 +305,10 @@ mouse over a date in F<etc/RT_SiteConfig.pm>:
     Set(@CalendarPopupFields, ('Status', 'OwnerObj->Name', 'DueObj->ISO'));
 
 You can set the order that the events will presented in the day cell with
-the C<$CalendarSortCalendarEvents> setting. The following example sorts
+the C<$CalendarSortEvents> setting. The following example sorts
 the events by status:
 
-    Set($CalendarSortCalendarEvents, sub {
+    Set($CalendarSortEvents, sub {
         my @Tickets = @_;
         my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
         return @SortedTickets;

commit 7b22f4f1274e65c3ad6a3eba6ee703c7b11431f7
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 14:36:36 2023 -0300

    Small code formatting for better readability

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index b0eb038..dd3a6fd 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -100,6 +100,7 @@ sub FindTickets {
                 my $DateObj = $Date . "Obj";
                 $dateindex = LocalDate( $Ticket->$DateObj->Unix );
             }
+
             push @{ $Tickets{$dateindex } },
                 $Ticket
 

commit 6207d7547be40a154b30a01ec05f2aca291a5e22
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 14:18:47 2023 -0300

    Refactor Custom Field checking for the Calendar
    
    Remove pre-loading of all Custom Fields, and instead only load the
    Custom Fields that are used by the Calendar, declared on the Format
    attribute.
    
    Also, update the way that the date content of the custom field was parsed
    for consistency. Instead of using regex, we now load it to a Date Object
    and parse it with LocalDate.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index cbd1903..b0eb038 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -79,9 +79,23 @@ sub FindTickets {
             if ($Date =~ /^CF\./){
                 my $cf = $Date;
                 $cf =~ s/^CF\.\{(.*)\}/$1/;
-                $dateindex = $Ticket->FirstCustomFieldValue($cf);
-                next unless $dateindex;
-                $dateindex =~ s/(.*) (.*)/$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 );

commit 262d72628e5d4cbe0d06b7e147afc667ae2d074c
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 24 14:05:03 2023 -0300

    Add comment about $dateindex to explain how it's used
    
    When we added the date and date time Custom Field support to the
    calendar, we added a bit of complexity to the code and needed to
    add use of a new variable, $dateindex, to extract the correct date
    from the Custom Field value and index the Ticket into the correct
    date bucket.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index e0b9390..cbd1903 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -71,6 +71,10 @@ sub FindTickets {
 
         # How to find the LastContacted date ?
         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;

commit 43140bd7ab6cd151d3ab5ec8b96b43d29f03136b
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Wed Aug 23 17:14:10 2023 -0300

    Add Date Custom Field support to the calendar
    
    Only Date Time custom fields were supported in the calendar. This patch
    adds support for Date custom fields.
    
    Also, change the way we check for the Custom Field type, allowing loading
    all Date and Date Time custom fields even if they are not in the Format
    attribute.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 2f7a599..b037886 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -261,17 +261,6 @@ my $title = loc("Calendar");
 
 my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
 
-# push Date Time CFs to DateTypes
-my $CFs = RT::CustomFields->new($session{'CurrentUser'});
-$CFs->Limit(
-  FIELD => 'Type',
-  VALUE => 'DateTime',
-);
-
-while ( my $CF = $CFs->Next ) {
-  push @DateTypes, 'CustomField.{' . $CF->Name . '}';
-}
-
 my $rtdate = RT::Date->new($session{'CurrentUser'});
 
 my $weekstart = 'Sunday'; #RT::SiteConfig?  user pref?
@@ -329,12 +318,22 @@ $TempQuery  = $Query  if $Query;
 $TempFormat = $Format if $Format;
 
 # we search all date types in Format string
-my @Dates = grep {
-  my $LintField=${_};
-  $LintField=~s/(\{|\})/\\$1/;
-  $TempFormat =~ m/__$LintField(Relative)?__/
-} @DateTypes;
+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);
 @Dates = map { $_ =~ s/^CustomField\.(.*)$/CF.$1/; $_ } @Dates;
 
 # used to display or not a date in Element/CalendarEvent

commit b5488423f7aa9591613b08a07836f126357d9da5
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Wed Aug 23 16:15:26 2023 -0300

    Remove general icon from non match events
    
    If custom icon is not set to a Custom Field based event, then we will
    not show a standard icon for that.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 5ebd0f9..e0b9390 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -202,8 +202,6 @@ sub GetEventIcon {
                and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $CurrentDate) {
         $EventIcon = '<img src="' . $RT::WebImagesURL .  '/updated.png" />';
 
-    } else {
-        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/reminder.png" />';
     }
     return $EventIcon;
 }

commit 8299ab50c7e1647b587bd3f554f7a4120ca15188
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 22 14:44:21 2023 -0300

    Add button to Download Calendar events
    
    Add button that download the events of the calendar that are shown in the
    current view.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 3ccc107..2f7a599 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -56,12 +56,14 @@ $FilterOnStatusClear => undef
 % }
           </select>
       </div>
-      <div class="col-2 text-left">
+      <div class="col text-left">
 % if ($FilterOnStatus[0]) {
         <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
           value="1" class="button btn btn-primary"><% loc('Clear') %></button>
 % }
         <input type="submit" value="<% loc('Filter') %>" class="button btn btn-primary" />
+        <a href="<%$RT::WebPath%>/Search/Results.tsv?<%$DownloadQueryString%>"
+          class="button btn btn-primary ml-2"><% loc('Download as Spreadsheet') %></a>
       </div>
     </div>
     </div>
@@ -344,6 +346,15 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
 
 my %Tickets = 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 %StatusesAvailable;
 for my $Dates (keys %Tickets) {
     my $TicketsOnTheDate = $Tickets{$Dates};

commit e950a511ea92100b33a002f1bba0078f2e206e3a
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 22 14:17:11 2023 -0300

    Add Status Filter to Calendar
    
    Allow user to filter events by status.
    
    Also update the table header to be a bootstrap div container instead
    of a table, so it can accommodate the new filter better.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 71ea271..3ccc107 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -7,6 +7,9 @@ $Order => undef
 $OrderBy => undef
 $RowsPerPage => undef
 $NewQuery => 0
+ at FilterOnStatus => undef
+$BaseQuery => undef
+$FilterOnStatusClear => undef
 </%args>
 
 <& /Elements/Header, Title => $title &>
@@ -22,29 +25,57 @@ $NewQuery => 0
 <&| /Widgets/TitleBox,
      title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year) &>
 
-<table width="100%">
-<tr>
-<td align="left">
+<form id="FilterOnStatusForm"
+  action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+  <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
+  <div class="container col-12 mt-1 mb-1">
+    <div class="row align-items-center">
+      <div class="col-1 text-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">
+        <a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
+      </div>
+      <div class="col text-center">
+        <div class="row">
+        <div class="col-2">
+          <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
+        </div>
+        <div class="col-2 label"><% loc( 'Filter on [_1]', loc('Status') ) %>:</div>
+      <div class="col-3">
+          <select name="FilterOnStatus" id="FilterOnStatus"
+            class="selectpicker form-control tall" multiple="multiple" size="6">
+% for my $Status (sort keys %StatusesAvailable) {
+            <option value="<% $Status %>"
+% if (@FilterOnStatus && $FilterOnStatus[0]) {
+              <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
+% }
+            ><% loc($Status) %></option>
+% }
+          </select>
+      </div>
+      <div class="col-2 text-left">
+% if ($FilterOnStatus[0]) {
+        <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
+          value="1" class="button btn btn-primary"><% loc('Clear') %></button>
+% }
+        <input type="submit" value="<% loc('Filter') %>" class="button btn btn-primary" />
+      </div>
+    </div>
+    </div>
+    <div class="col-1 text-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>
+      <a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
+    </div>
+  </div>
+</div>
+</form>
 
 <table class="rtxcalendar">
 
@@ -219,6 +250,11 @@ my %legend = (
 
 </%ONCE>
 <%INIT>
+if ($FilterOnStatusClear) {
+  $Query = $BaseQuery if $BaseQuery;
+  @FilterOnStatus = ();
+}
+$BaseQuery ||= $Query;
 my $title = loc("Calendar");
 
 my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
@@ -256,10 +292,16 @@ 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)";
+}
+
 my $QueryString =
       $m->comp(
         '/Elements/QueryString',
-        Query   => $Query,
+        Query   => $BaseQuery,
         Format  => $Format,
         Order   => $Order,
         OrderBy => $OrderBy,
@@ -302,6 +344,15 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
 
 my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
+my %StatusesAvailable;
+for my $Dates (keys %Tickets) {
+    my $TicketsOnTheDate = $Tickets{$Dates};
+    for my $Ticket (@$TicketsOnTheDate) {
+        my $Status = $Ticket->Status;
+        $StatusesAvailable{$Status}++;
+    }
+}
+
 my $SortCalendarEvents = sub {
   return @_;
 };

commit 5c6a9fd71ac85a15a869b1b821586fccb3949abf
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 17 15:44:01 2023 -0300

    Update the legend of the Calendar
    
    Upgrade the code of the legend area to match bootstrap 4.
    
    Added the custom icons to the legend.
    
    Added the custom colors and their statuses to the legend.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 5225cca..71ea271 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -116,23 +116,54 @@ $NewQuery => 0
 </tr>
 </table>
 
-<table width="100%">
-% foreach my $legend (sort keys %legend) {
-    <tr>
-      <td align="right">
-        <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" />
-      </td>
-      <td align="left">
-%       my $more = 0;
-%       foreach ( @{$legend{$legend}} ) {
-          <% $more++ ? ', ' : '' %>
-          <&|/l&><% $_ %></&>
+<div class="container mt-4">
+  <div class="row">
+    <div class="col-6">
+      <div class="row">
+        <div class="col-2 pl-1 pr-1"> </div>
+        <div class="col-auto text-left pl-1 pr-1"><b><&|/l&>Event Types</&></b></div>
+      </div>
+%     foreach my $legend (sort keys %legend) {
+        <div class="row">
+          <div class="col-3 text-right pl-1 pr-1">
+            <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" />
+          </div>
+          <div class="col text-left pl-1 pr-1">
+%           my $more = 0;
+%           foreach ( @{$legend{$legend}} ) {
+              <% $more++ ? ', ' : '' %>
+              <&|/l&><% $_ %></&>
+%           }
+          </div>
+        </div>
+%     }
+%     if (RT->Config->Get('CalendarCustomIcons')) {
+%       my %CalendarCustomIcons = RT->Config->Get('CalendarCustomIcons');
+%       foreach my $legend (sort keys %CalendarCustomIcons) {
+%         my $legendLabel = $legend;
+%         $legendLabel =~ s/^CF\.\{(.*)\}/$1/;
+          <div class="row">
+            <div class="col-3 text-right pl-1 pr-1">
+              <span class="tip"><% $CalendarCustomIcons{$legend}|n %></span>
+            </div>
+            <div class="col text-left pl-1 pr-1">
+              <&|/l&><% $legendLabel %></&>
+            </div>
+          </div>
 %       }
-      </td>
-    </tr>
-% }
-
-</table>
+%     }
+    </div>
+    <div class="col-6">
+%     if (RT->Config->Get('CalendarColorStatusMap')) {
+        <b><&|/l&>State Colors</&></b><br />
+%       my %ColorStatusMap = RT->Config->Get('CalendarColorStatusMap');
+%         foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
+            <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
+%         }
+%     }
+    </div>
+  </div>
+</div>
 
 </&>
 

commit 3a7a63e931398674a710c6a6d4649302e8f1c2eb
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 17 15:43:46 2023 -0300

    Add feature to customize Icon of events on the calendar
    
    Since we allow now to add custom fields to the calendar, we need to
    allow to customize the icon of the event based on its type.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index ca374f7..9a6750f 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -6,45 +6,7 @@ $DateTypes => undef
 <div class="day">
 <small>
 
-% if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
-     <img src="<%$RT::WebImagesURL%>/reminder.png" />
-
-% } elsif ($DateTypes->{Resolved}
-%           and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $today) {
-         <img src="<%$RT::WebImagesURL%>/resolved.png" />
-
-% } elsif ($DateTypes->{Starts} and $DateTypes->{Due}
-%           and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today ) {
-    <img src="<%$RT::WebImagesURL%>/starts_due.png" />
-
-% } elsif ($DateTypes->{Due} and $DateTypes->{Created}
-%           and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today ) {
-    <img src="<%$RT::WebImagesURL%>/created_due.png" />
-
-% } elsif ($DateTypes->{Starts}
-%           and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/starts.png" />
-
-% } elsif ($DateTypes->{Due}
-%           and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/due.png" />
-
-% } elsif ($DateTypes->{Created}
-%           and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/created.png" />
-
-% } elsif ($DateTypes->{Started}
-%           and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/started.png" />
-
-% } elsif ($DateTypes->{LastUpdated}
-%           and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/updated.png" />
-
-% } else {
-    <img src="<%$RT::WebImagesURL%>/reminder.png" />
-% }
-
+    <% RTx::Calendar::GetEventIcon($Object, $today, $DateTypes, $IsReminder)|n %>
 	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>" <% $StatusColor|n %>>
            <% $Object->QueueObj->Name %> #<% $TicketId %>
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 61eeb5d..5ebd0f9 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -141,6 +141,73 @@ sub SearchDefaultCalendar {
     }
 }
 
+sub GetEventIcon {
+    my $Object   = shift;
+    my $CurrentDate = shift;
+    my $DateTypes = shift;
+    my $IsReminder  = shift;
+    my $EventIcon;
+    if (RT->Config->Get('CalendarCustomIcons')) {
+        my %CalendarCustomIcons = RT->Config->Get('CalendarCustomIcons');
+        for my $DateField (keys %CalendarCustomIcons) {
+            my $DateValue;
+            if ($DateField =~ /^CF\./){
+                my $cf = $DateField;
+                $cf =~ s/^CF\.\{(.*)\}/$1/;
+                $DateValue = $Object->FirstCustomFieldValue($cf);
+                next unless $DateValue;
+                $DateValue =~ s/(.*) (.*)/$1/;
+            } else {
+                my $DateObj = $DateField . "Obj";
+                $DateValue = LocalDate( $Object->$DateObj->Unix );
+            }
+            if ($DateValue eq $CurrentDate) {
+                $EventIcon = $CalendarCustomIcons{$DateField};
+                return $EventIcon;
+            }
+        }
+    }
+    if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/reminder.png" />';
+
+    } elsif ($DateTypes->{Resolved}
+               and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $CurrentDate) {
+            $EventIcon = '<img src="' . $RT::WebImagesURL .  '/resolved.png" />';
+
+    } elsif ($DateTypes->{Starts} and $DateTypes->{Due}
+               and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $CurrentDate and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate ) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/starts_due.png" />';
+
+    } elsif ($DateTypes->{Due} and $DateTypes->{Created}
+               and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $CurrentDate ) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/created_due.png" />';
+
+    } elsif ($DateTypes->{Starts}
+               and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $CurrentDate) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/starts.png" />';
+
+    } elsif ($DateTypes->{Due}
+               and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $CurrentDate) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/due.png" />';
+
+    } elsif ($DateTypes->{Created}
+               and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $CurrentDate) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/created.png" />';
+
+    } elsif ($DateTypes->{Started}
+               and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $CurrentDate) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/started.png" />';
+
+    } elsif ($DateTypes->{LastUpdated}
+               and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $CurrentDate) {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/updated.png" />';
+
+    } else {
+        $EventIcon = '<img src="' . $RT::WebImagesURL .  '/reminder.png" />';
+    }
+    return $EventIcon;
+}
+
 1;
 
 __END__
@@ -241,6 +308,14 @@ adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
         'resolved'                              => '#aaa',
     ));
 
+Custom Icons can be defined for the events in the calendar by adding the
+C<$CalendarCustomIcons> setting to your F<etc/RT_SiteConfig.pm>:
+
+    Set(%CalendarCustomIcons, (
+        'CF.{Maintenance Estimated Start Date/Time - ET}'
+            => '<small class="ml-1"><i class="fas fa-cog"></i></small>'
+    ));
+
 =head1 USAGE
 
 A small help section is available in /Search/Calendar.html

commit d280bf44f1f9c0cafbec71303b726c82b3beaebf
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 17 15:32:18 2023 -0300

    Add feature to customize color of events on the calendar

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 0f891b1..ca374f7 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -45,7 +45,7 @@ $DateTypes => undef
     <img src="<%$RT::WebImagesURL%>/reminder.png" />
 % }
 
-	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>" <% $StatusColor|n %>>
            <% $Object->QueueObj->Name %> #<% $TicketId %>
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
            <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br />
@@ -88,17 +88,30 @@ my $TicketId;
 my $ticket;
 my $subject;
 my $IsReminder;
+my $status;
 
 if ($Object->Type eq 'reminder') {
     $IsReminder = 1;
     if ($Object->RefersTo->First) {
-	$ticket   = $Object->RefersTo->First->TargetObj;
-	$TicketId = $ticket->Id;
-	$subject = $Object->Subject . " (" . $ticket->Subject . ")";
+        $ticket   = $Object->RefersTo->First->TargetObj;
+        $TicketId = $ticket->Id;
+        $subject = $Object->Subject . " (" . $ticket->Subject . ")";
+        $status = $ticket->Status;
     }
 } else {
     $TicketId = $Object->Id;
     $subject = $Object->Subject;
+    $status = $Object->Status;
+}
+
+# Colors per status
+my %CalendarColorStatusMap;
+if (RT->Config->Get('CalendarColorStatusMap')) {
+    %CalendarColorStatusMap = RT->Config->Get('CalendarColorStatusMap');
+}
+my $StatusColor = '';
+if ($CalendarColorStatusMap{$status}) {
+    $StatusColor ='style="color: '.$CalendarColorStatusMap{$status}.'"';
 }
 
 my $display_owner = $RT::CalendarDisplayOwner;
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 6413b9c..61eeb5d 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -230,6 +230,17 @@ the events by status:
         return @SortedTickets;
     });
 
+It's also possible to change the color of the events in the calendar by
+adding the C<$CalendarColorStatusMap> setting to your F<etc/RT_SiteConfig.pm>:
+
+    Set(%CalendarColorStatusMap, (
+        'new'                                   => 'blue',
+        'open'                                  => 'blue',
+        'approved'                              => 'green',
+        'rejected'                              => 'red',
+        'resolved'                              => '#aaa',
+    ));
+
 =head1 USAGE
 
 A small help section is available in /Search/Calendar.html

commit 46b06b2cd10ef5a79c408abdf20bb3b6623d8916
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Wed Aug 16 11:45:20 2023 -0300

    Add setting to allow customize the sort order of the events
    
    Add $CalendarSortCalendarEvents setting to allow customize the sort order
    of the events in a day cell of the calendar. This is a sub that takes
    the Objects list (that can be tickets or reminders) and returns a sorted
    list of the objects.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 00caeff..5225cca 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -68,7 +68,7 @@ $NewQuery => 0
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
 
-%     for my $t ( @{ $Tickets{$date->strftime("%F")} } ) {
+%     for my $t ( $SortCalendarEvents->( @{ $Tickets{ $date->strftime("%F") } || [] } )) {
         <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
 %     }
 
@@ -271,4 +271,10 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
 
 my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
+my $SortCalendarEvents = sub {
+  return @_;
+};
+if (RT->Config->Get("CalendarSortCalendarEvents")) {
+  $SortCalendarEvents = RT->Config->Get("CalendarSortCalendarEvents");
+}
 </%INIT>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 3f03c77..6413b9c 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -220,6 +220,16 @@ mouse over a date in F<etc/RT_SiteConfig.pm>:
 
     Set(@CalendarPopupFields, ('Status', 'OwnerObj->Name', 'DueObj->ISO'));
 
+You can set the order that the events will presented in the day cell with
+the C<$CalendarSortCalendarEvents> setting. The following example sorts
+the events by status:
+
+    Set($CalendarSortCalendarEvents, sub {
+        my @Tickets = @_;
+        my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
+        return @SortedTickets;
+    });
+
 =head1 USAGE
 
 A small help section is available in /Search/Calendar.html

commit 20a529c4f13e061592d8c0391a674a0df4d900a8
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Jul 28 16:10:17 2023 -0300

    Allow date time Custom Fields to be used in Calendar
    
    It's possible now to add __CustomField.{Custom Field Name}__ to search
    format so it will appear on calendar.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 5b8a6c5..0f891b1 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -41,6 +41,8 @@ $DateTypes => undef
 %           and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $today) {
     <img src="<%$RT::WebImagesURL%>/updated.png" />
 
+% } else {
+    <img src="<%$RT::WebImagesURL%>/reminder.png" />
 % }
 
 	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
@@ -60,6 +62,9 @@ $DateTypes => undef
 %
 %    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' )/;
@@ -123,6 +128,7 @@ 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;
 }
 
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 9c5e71e..00caeff 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -192,6 +192,17 @@ my $title = loc("Calendar");
 
 my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
 
+# push Date Time CFs to DateTypes
+my $CFs = RT::CustomFields->new($session{'CurrentUser'});
+$CFs->Limit(
+  FIELD => 'Type',
+  VALUE => 'DateTime',
+);
+
+while ( my $CF = $CFs->Next ) {
+  push @DateTypes, 'CustomField.{' . $CF->Name . '}';
+}
+
 my $rtdate = RT::Date->new($session{'CurrentUser'});
 
 my $weekstart = 'Sunday'; #RT::SiteConfig?  user pref?
@@ -243,7 +254,13 @@ $TempQuery  = $Query  if $Query;
 $TempFormat = $Format if $Format;
 
 # we search all date types in Format string
-my @Dates = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
+my @Dates = grep {
+  my $LintField=${_};
+  $LintField=~s/(\{|\})/\\$1/;
+  $TempFormat =~ m/__$LintField(Relative)?__/
+} @DateTypes;
+
+ at Dates = map { $_ =~ s/^CustomField\.(.*)$/CF.$1/; $_ } @Dates;
 
 # used to display or not a date in Element/CalendarEvent
 my %DateTypes = map { $_ => 1 } @Dates;
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 9bb375d..3f03c77 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -71,14 +71,24 @@ sub FindTickets {
 
         # How to find the LastContacted date ?
         for my $Date (@$Dates) {
-            my $DateObj = $Date . "Obj";
-            push @{ $Tickets{ LocalDate( $Ticket->$DateObj->Unix ) } },
+            my $dateindex;
+            if ($Date =~ /^CF\./){
+                my $cf = $Date;
+                $cf =~ s/^CF\.\{(.*)\}/$1/;
+                $dateindex = $Ticket->FirstCustomFieldValue($cf);
+                next unless $dateindex;
+                $dateindex =~ s/(.*) (.*)/$1/;
+            } else {
+                my $DateObj = $Date . "Obj";
+                $dateindex = LocalDate( $Ticket->$DateObj->Unix );
+            }
+            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{ LocalDate( $Ticket->$DateObj->Unix ) }
+                or $AlreadySeen{ $dateindex }
                 {$Ticket}++;
         }
     }

-----------------------------------------------------------------------


hooks/post-receive
-- 
rtx-calendar


More information about the Bps-public-commit mailing list