From ec69cca42a6f5f97f0919f5fda703e25330697bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rajmund=20Hru=C5=A1ka?= <rajmund.hruska@cesnet.cz>
Date: Tue, 16 May 2023 16:04:02 +0200
Subject: [PATCH] Feature: Replace eonasdan-bootstrap-datetimepicker with
 tempus dominus (Redmine issue: #7651)

---
 Gruntfile.js                                  |  10 +-
 .../design_bs3/templates/_layout.html         |   4 +-
 .../templates/_layout_events_search.html      |   8 +-
 lib/hawat/static/css/hawat.css                |   5 +
 lib/hawat/static/js/hawat-jquery.js           | 197 ++++++++----------
 package.json                                  |   2 +-
 yarn.lock                                     |  14 +-
 7 files changed, 115 insertions(+), 125 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index a2c6cd72..ea13e74e 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -182,20 +182,20 @@ module.exports = function(grunt) {
                         src: './*',
                         dest: '<%= project_paths.web_static_dir %>vendor/bootstrap-select/js/'
                     },
-                    // ----- eonasdan-bootstrap-datetimepicker
+                    // ----- tempus-dominus
                     {
                         expand: true,
                         flatten: true,
-                        cwd: 'node_modules/eonasdan-bootstrap-datetimepicker/build/css/',
+                        cwd: 'node_modules/@eonasdan/tempus-dominus/dist/css/',
                         src: './*',
-                        dest: '<%= project_paths.web_static_dir %>vendor/bootstrap-datetimepicker/css/'
+                        dest: '<%= project_paths.web_static_dir %>vendor/@eonasdan/tempus-dominus/css/'
                     },
                     {
                         expand: true,
                         flatten: true,
-                        cwd: 'node_modules/eonasdan-bootstrap-datetimepicker/build/js/',
+                        cwd: 'node_modules/@eonasdan/tempus-dominus/dist/js/',
                         src: './*',
-                        dest: '<%= project_paths.web_static_dir %>vendor/bootstrap-datetimepicker/js/'
+                        dest: '<%= project_paths.web_static_dir %>vendor/@eonasdan/tempus-dominus/js/'
                     },
                     // ----- Font-Awesome Free
                     {
diff --git a/lib/hawat/blueprints/design_bs3/templates/_layout.html b/lib/hawat/blueprints/design_bs3/templates/_layout.html
index 48a42386..a49dd356 100644
--- a/lib/hawat/blueprints/design_bs3/templates/_layout.html
+++ b/lib/hawat/blueprints/design_bs3/templates/_layout.html
@@ -34,7 +34,7 @@
         <link rel="stylesheet" href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap-theme.min.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='vendor/bootstrap-select/css/bootstrap-select.min.css') }}">
-        <link rel="stylesheet" href="{{ url_for('static', filename='vendor/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css') }}">
+        <link rel="stylesheet" href="{{ url_for('static', filename='vendor/@eonasdan/tempus-dominus/css/tempus-dominus.min.css') }}">
 
         <!-- NVD3 CSS -->
         <link rel="stylesheet" href="{{ url_for('static', filename='vendor/nvd3/css/nv.d3.min.css') }}">
@@ -283,7 +283,7 @@
         <!-- Optional JavaScript ---------------------------------------------->
         <script src="{{ url_for('static', filename='vendor/bootstrap/js/bootstrap.min.js') }}"></script>
         <script src="{{ url_for('static', filename='vendor/bootstrap-select/js/bootstrap-select.min.js') }}"></script>
-        <script src="{{ url_for('static', filename='vendor/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js') }}"></script>
+        <script src="{{ url_for('static', filename='vendor/@eonasdan/tempus-dominus/js/tempus-dominus.min.js') }}"></script>
         <script src="{{ url_for('static', filename='js/hawat-jquery.js') }}"></script>
     {%- endblock bodyjs %}
 
diff --git a/lib/hawat/blueprints/design_bs3/templates/_layout_events_search.html b/lib/hawat/blueprints/design_bs3/templates/_layout_events_search.html
index d608c525..80acffcf 100644
--- a/lib/hawat/blueprints/design_bs3/templates/_layout_events_search.html
+++ b/lib/hawat/blueprints/design_bs3/templates/_layout_events_search.html
@@ -86,7 +86,7 @@
                             <div class="form-group{{ frmctrlhdn }}" id="form-group-detecttime">
                                 <div class="col-sm-6">
                                     {%- call macros_form.render_form_label_help_html(g.search_form.dt_from) %}{%- endcall %}
-                                    <div id="datetimepicker-from-1" class="input-group date datetimepicker-ymdhms-from widget-datetimepicker">
+                                    <div id="datetimepicker-from" class="input-group date">
                                         {{ g.search_form.dt_from(class_='form-control', disabled=frmctrldsb) }}
                                         <span class="input-group-addon">
                                             {{ get_icon('calendar') }}
@@ -97,7 +97,7 @@
                                 </div>
                                 <div class="col-sm-6">
                                     {%- call macros_form.render_form_label_help_html(g.search_form.dt_to) %}{%- endcall %}
-                                    <div id="datetimepicker-to-1" class="input-group date datetimepicker-ymdhms-to widget-datetimepicker">
+                                    <div id="datetimepicker-to" class="input-group date">
                                         {{ g.search_form.dt_to(class_='form-control', disabled=frmctrldsb) }}
                                         <span class="input-group-addon">
                                             {{ get_icon('calendar') }}
@@ -116,7 +116,7 @@
                                         {{ macros_form.render_help_idea_reference() }}
                                         {{ macros_form.render_help_datetime() }}
                                     {%- endcall %}
-                                    <div id="datetimepicker-from-2" class="input-group date datetimepicker-ymdhms-from widget-datetimepicker">
+                                    <div id="datetimepicker-from-2" class="input-group date">
                                         {{ g.search_form.st_from(class_='form-control', disabled=frmctrldsb) }}
                                         <span class="input-group-addon">
                                             {{ get_icon('calendar') }}
@@ -130,7 +130,7 @@
                                         {{ macros_form.render_help_idea_reference() }}
                                         {{ macros_form.render_help_datetime() }}
                                     {%- endcall %}
-                                    <div id="datetimepicker-to-2" class="input-group date datetimepicker-ymdhms-to widget-datetimepicker">
+                                    <div id="datetimepicker-to-2" class="input-group date">
                                         {{ g.search_form.st_to(class_='form-control', disabled=frmctrldsb) }}
                                         <span class="input-group-addon">
                                             {{ get_icon('calendar') }}
diff --git a/lib/hawat/static/css/hawat.css b/lib/hawat/static/css/hawat.css
index 0f025229..ab9a983c 100644
--- a/lib/hawat/static/css/hawat.css
+++ b/lib/hawat/static/css/hawat.css
@@ -184,6 +184,11 @@ body {
     overflow-x: auto;
 }
 
+/* Change the default width of the datetime picker box. */
+.tempus-dominus-widget {
+    width: 25rem;
+}
+
 /*******************************************************************************
 
     Custom style for timeline dataset tables.
diff --git a/lib/hawat/static/js/hawat-jquery.js b/lib/hawat/static/js/hawat-jquery.js
index 0c6cbb9a..6806954b 100644
--- a/lib/hawat/static/js/hawat-jquery.js
+++ b/lib/hawat/static/js/hawat-jquery.js
@@ -6,113 +6,100 @@
 
 $(function() {
 
-    // Initialize date and datetime picker components.
-    //$('.datepicker').datetimepicker({
-    //    //locale: 'en',
-    //    format: 'YYYY-MM-DD'
-    //});
-    //$('.datetimepicker').datetimepicker({
-    //    //locale: 'en',
-    //    format: 'YYYY-MM-DD HH:mm:ss'
-    //});
-
-    // Initialize linked date and datetime picker components.
-    $('#datetimepicker-from').datetimepicker({
-        locale: GLOBAL_LOCALE,
-        format: 'YYYY-MM-DD HH:mm:ss',
-        icons: {
-            time: "fas fa-fw fa-clock",
-            date: "fas fa-fw fa-calendar-alt",
-            up:   "fas fa-fw fa-arrow-up",
-            down: "fas fa-fw fa-arrow-down"
-        }
-    });
-    $('#datetimepicker-to').datetimepicker({
-        useCurrent: false, //Important! See issue #1075
-        locale: GLOBAL_LOCALE,
-        format: 'YYYY-MM-DD HH:mm:ss',
-        icons: {
-            time: "fas fa-fw fa-clock",
-            date: "fas fa-fw fa-calendar-alt",
-            up:   "fas fa-fw fa-arrow-up",
-            down: "fas fa-fw fa-arrow-down"
-        }
-    });
-    $("#datetimepicker-from").on("dp.change", function (e) {
-        $('#datetimepicker-to').data("DateTimePicker").minDate(e.date);
-    });
-    $("#datetimepicker-to").on("dp.change", function (e) {
-        $('#datetimepicker-from').data("DateTimePicker").maxDate(e.date);
-    });
-
-    // Initialize linked date and datetime picker components.
-    $('.datetimepicker-ymdhms-from').datetimepicker({
-        locale: GLOBAL_LOCALE,
-        format: 'YYYY-MM-DD HH:mm:ss',
-        icons: {
-            time: "fas fa-fw fa-clock",
-            date: "fas fa-fw fa-calendar-alt",
-            up:   "fas fa-fw fa-arrow-up",
-            down: "fas fa-fw fa-arrow-down"
-        }
-    });
-    $('.datetimepicker-ymdhms-to').datetimepicker({
-        useCurrent: false, //Important! See issue #1075
-        locale: GLOBAL_LOCALE,
-        format: 'YYYY-MM-DD HH:mm:ss',
-        icons: {
-            time: "fas fa-fw fa-clock",
-            date: "fas fa-fw fa-calendar-alt",
-            up:   "fas fa-fw fa-arrow-up",
-            down: "fas fa-fw fa-arrow-down"
+    const createTempusDominusInstance = (elementId, options) => {
+        const element = document.getElementById(elementId);
+        return new tempusDominus.TempusDominus(element, options);
+    };
+    const createCommonOptions = (_format, _useSeconds) => {
+        return {
+            localization: {
+                locale: GLOBAL_LOCALE,
+                format: _format,
+                startOfTheWeek: 1,
+                hourCycle: 'h23'
+            },
+            display: {
+                icons: {
+                    time: "fas fa-fw fa-clock",
+                    date: "fas fa-fw fa-calendar-alt",
+                    up: "fas fa-fw fa-arrow-up",
+                    down: "fas fa-fw fa-arrow-down",
+                    previous: 'fas fa-chevron-left',
+                    next: 'fas fa-chevron-right'
+                },
+                components: {
+                    seconds: _useSeconds
+                }
+            }
         }
-    });
-    $("#datetimepicker-from-1").on("dp.change", function (e) {
-        $('#datetimepicker-to-1').data("DateTimePicker").minDate(e.date);
-    });
-    $("#datetimepicker-to-1").on("dp.change", function (e) {
-        $('#datetimepicker-from-1').data("DateTimePicker").maxDate(e.date);
-    });
-    $("#datetimepicker-from-2").on("dp.change", function (e) {
-        $('#datetimepicker-to-2').data("DateTimePicker").minDate(e.date);
-    });
-    $("#datetimepicker-to-2").on("dp.change", function (e) {
-        $('#datetimepicker-from-2').data("DateTimePicker").maxDate(e.date);
-    });
+    };
 
-    // Disable built-in JS form validation in case the form`s cancel button is clicked.`
-    $('input[type="submit"][value="Cancel"]').click(function(){
-        $(this).parents('form:first').attr('novalidate', 'novalidate');
-    });
-
-    // Initialize linked date and datetime picker components.
-    $('#datetimepicker-hm-from').datetimepicker({
-        locale: GLOBAL_LOCALE,
-        format: 'YYYY-MM-DD HH:mm',
-        icons: {
-            time: "fas fa-fw fa-clock",
-            date: "fas fa-fw fa-calendar-alt",
-            up:   "fas fa-fw fa-arrow-up",
-            down: "fas fa-fw fa-arrow-down"
-        }
-    });
-    $('#datetimepicker-hm-to').datetimepicker({
-        useCurrent: false, //Important! See issue #1075
-        locale: GLOBAL_LOCALE,
-        format: 'YYYY-MM-DD HH:mm',
-        icons: {
-            time: "fas fa-fw fa-clock",
-            date: "fas fa-fw fa-calendar-alt",
-            up:   "fas fa-fw fa-arrow-up",
-            down: "fas fa-fw fa-arrow-down"
-        }
-    });
-    $("#datetimepicker-hm-from").on("dp.change", function (e) {
-        $('#datetimepicker-hm-to').data("DateTimePicker").minDate(e.date);
-    });
-    $("#datetimepicker-hm-to").on("dp.change", function (e) {
-        $('#datetimepicker-hm-from').data("DateTimePicker").maxDate(e.date);
-    });
+    // Initialize date and datetime picker components.
+    const commonOptionsSeconds = createCommonOptions('yyyy-MM-dd HH:mm:ss', true);
+    if (document.getElementById('datetimepicker-from') && document.getElementById('datetimepicker-to')) {
+        const linked_from_1 = createTempusDominusInstance('datetimepicker-from', commonOptionsSeconds);
+        const linked_to_1 = createTempusDominusInstance('datetimepicker-to', {
+            useCurrent: false,
+            ...commonOptionsSeconds
+        });
+        document.getElementById('datetimepicker-from').addEventListener(tempusDominus.Namespace.events.change, (e) => {
+            linked_to_1.updateOptions({
+                restrictions: {
+                    minDate: e.detail.date,
+                },
+            });
+        });
+        document.getElementById('datetimepicker-to').addEventListener(tempusDominus.Namespace.events.change, (e) => {
+            linked_from_1.updateOptions({
+                restrictions: {
+                    maxDate: e.detail.date,
+                },
+            });
+        });
+    }
+    if (document.getElementById('datetimepicker-from-2') && document.getElementById('datetimepicker-to-2')) {
+        const linked_from_2 = createTempusDominusInstance('datetimepicker-from-2', commonOptionsSeconds);
+        const linked_to_2 = createTempusDominusInstance('datetimepicker-to-2', {
+            useCurrent: false,
+            ...commonOptionsSeconds
+        });
+        document.getElementById('datetimepicker-from-2').addEventListener(tempusDominus.Namespace.events.change, (e) => {
+            linked_to_2.updateOptions({
+                restrictions: {
+                    minDate: e.detail.date,
+                },
+            });
+        });
+        document.getElementById('datetimepicker-to-2').addEventListener(tempusDominus.Namespace.events.change, (e) => {
+            linked_from_2.updateOptions({
+                restrictions: {
+                    maxDate: e.detail.date,
+                },
+            });
+        });
+    }
+    const commonOptions = createCommonOptions('yyyy-MM-dd HH:mm', false);
+    if (document.getElementById('datetimepicker-hm-from') && document.getElementById('datetimepicker-hm-to')) {
+        const linked_from = createTempusDominusInstance('datetimepicker-hm-from', commonOptions);
+        const linked_to = createTempusDominusInstance('datetimepicker-hm-to', {
+            useCurrent: false,
+            ...commonOptions
+        });
+        document.getElementById('datetimepicker-hm-from').addEventListener(tempusDominus.Namespace.events.change, (e) => {
+            linked_to.updateOptions({
+                restrictions: {
+                    minDate: e.detail.date,
+                },
+            });
+        });
+        document.getElementById('datetimepicker-hm-to').addEventListener(tempusDominus.Namespace.events.change, (e) => {
+            linked_from.updateOptions({
+                restrictions: {
+                    maxDate: e.detail.date,
+                },
+            });
+        });
+    }
 
     // Initialize select pickers.
     //$('.selectpicker').selectpicker();
diff --git a/package.json b/package.json
index a78cddc2..34db54e5 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
         "@fortawesome/fontawesome-free": "^6.4.0",
         "@popperjs/core": "^2.11.7",
         "bootstrap": "^3.4.0",
-        "bootstrap-datepicker": "^1.7.1",
+        "@eonasdan/tempus-dominus": "^6.7.7",
         "bootstrap-select": "^1.13.18",
         "bootswatch": "^5.2.3",
         "cldr-core": "^43.0.0",
diff --git a/yarn.lock b/yarn.lock
index 9463a32c..ff8a414c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,11 @@
 # yarn lockfile v1
 
 
+"@eonasdan/tempus-dominus@^6.7.7":
+  version "6.7.7"
+  resolved "https://registry.yarnpkg.com/@eonasdan/tempus-dominus/-/tempus-dominus-6.7.7.tgz#488c04ef775feb2779cbcc95d7715a188b29de9c"
+  integrity sha512-Ij/s+96iy1vDW/iVxIJEgsj/ChIM6Qk7R504uB55yObCFkrXzlWGSgyNJO7RNWiYmzcNtg/IHyBy5ZcrWJ+nrg==
+
 "@fortawesome/fontawesome-free@^6.4.0":
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz#1ee0c174e472c84b23cb46c995154dc383e3b4fe"
@@ -286,13 +291,6 @@ boom@2.x.x:
   dependencies:
     hoek "2.x.x"
 
-bootstrap-datepicker@^1.7.1:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/bootstrap-datepicker/-/bootstrap-datepicker-1.9.0.tgz#e4bfce3fcce1967876b21dc6833ec5994aaed090"
-  integrity sha512-9rYYbaVOheGYxjOr/+bJCmRPihfy+LkLSg4fIFMT9Od8WwWB/MB50w0JO1eBgKUMbb7PFHQD5uAfI3ArAxZRXA==
-  dependencies:
-    jquery ">=1.7.1 <4.0.0"
-
 bootstrap-select@^1.13.18:
   version "1.13.18"
   resolved "https://registry.yarnpkg.com/bootstrap-select/-/bootstrap-select-1.13.18.tgz#4557119d58dc1159189977161c803962220e4dda"
@@ -1851,7 +1849,7 @@ isstream@0.1.x, isstream@~0.1.2:
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
-"jquery@>=1.7.1 <4.0.0", "jquery@^1.8.3 || ^2.0 || ^3.0":
+"jquery@^1.8.3 || ^2.0 || ^3.0":
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
   integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
-- 
GitLab