]> _ Git - fluidbook-toolbox.git/commitdiff
wait #5662 @1.5
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Fri, 16 Jun 2023 10:15:21 +0000 (12:15 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Fri, 16 Jun 2023 10:15:21 +0000 (12:15 +0200)
app/Fluidbook/Stats.php
app/Http/Controllers/Admin/Operations/FluidbookPublication/StatsOperation.php
package-lock.json
package.json
public/packages/fluidbook/toolbox/css/stats.css [new file with mode: 0644]
public/packages/fluidbook/toolbox/css/stats.css.map [new file with mode: 0644]
public/packages/fluidbook/toolbox/css/stats.less [new file with mode: 0644]
resources/views/fluidbook_stats/loader.blade.php [deleted file]
resources/views/fluidbook_stats/stats.blade.php [new file with mode: 0644]
resources/views/fluidbook_stats/summary.blade.php [deleted file]

index f90763346807129d165aca749cfc79260117c2a2..f0175098c207a9a685ae50bf16fedec6d717947e 100644 (file)
@@ -226,6 +226,7 @@ class Stats extends Reporting
         $this->viewData->searches = $this->_processSearches();
         $this->viewData->outlinks = $this->_processOutgoingLinks();
         $this->viewData->countries = $this->_processCountries();
+        $this->viewData->shareDetails = $this->_processShareDetails();
 
         //=== MAIN PERIOD STATISTICS
         // These are the main statistics used to build the chart and the table below it.
@@ -311,6 +312,33 @@ class Stats extends Reporting
 
     }
 
+    protected function _processShareDetails()
+    {
+        $icons = [
+            'email' => 'las la-envelope',
+            'facebook' => 'lab la-facebook-f',
+            'twitter' => 'lab la-twitter',
+            'whatsapp' => 'lab la-whatsapp',
+            'linkedin' => 'lab la-linkedin-in',
+            'pinterest' => 'lab la-pinterest-p',
+            'googleplus' => 'lab la-google-plus',
+            'viadeo' => 'lab la-viadeo',
+        ];
+
+//        $res = [];
+//        foreach ($icons as $social => $icon) {
+//            $res[$social] = ['nb' => rand(1, 100), 'icon' => $icon];
+//        }
+//        return $res;
+
+        $events = collect($this->_eventsByPage['share']['subtable'] ?? [])->sortByDesc('nb_events');
+
+        foreach ($events as $event) {
+            $res[$event['label']] = ['nb' => $event['nb_events'], 'icon' => $icons[$event['label']]];
+        }
+        return $res;
+    }
+
     protected function getTooltipLabels()
     {
 
@@ -345,6 +373,7 @@ class Stats extends Reporting
                 'nb_downloads' => __('Téléchargements'),
                 'nb_prints' => __('Impressions'),
                 'nb_zooms' => __('Zooms'),
+                'nb_shares' => __('Partages'),
             ],
             // Per-page detail table
             'per-page' => [
@@ -352,7 +381,6 @@ class Stats extends Reporting
                 'nb_pageviews' => __('Vues'),
                 'nb_zooms' => __('Zooms'),
                 'nb_bookmarks' => __('Pages marquées'),
-                'nb_shares' => __('Partages'),
             ],
         ];
 
@@ -380,7 +408,7 @@ class Stats extends Reporting
             ->keyBy('label')
             ->map(function ($item, $key) {
                 // Make subtable data easier to lookup by keying them with the labels (ie. page numbers) for certain events
-                if (in_array($item['label'], ['zoom', 'bookmark', 'share']) && isset($item['subtable'])) {
+                if (in_array($item['label'], ['zoom', 'bookmark', 'share', 'video', 'slideshow']) && isset($item['subtable'])) {
                     $item['subtable'] = collect($item['subtable'])->keyBy(function ($item, $key) {
                         // Since there's some inconsistency in the way labels are stored (some have "page x" instead
                         // of just the number), we strip out any non-numeric values when making the keys
index a65b399273992aabf113d891f560f01ca89a943a..bb51875ab2366277722dfd9be97d114318de91be 100644 (file)
@@ -43,7 +43,7 @@ trait StatsOperation
         $data = $stats->processData($period_override);
         $data['locale'] = app()->getLocale();
         $data['fluidbook'] = $fluidbook;
-        return view('fluidbook_stats.loader', $data);
+        return view('fluidbook_stats.stats', $data);
 
     }
 
index 91dcc5624dfc6afdfa9d3cb891994581f566cbee..7114deb301c5d2edb56e0bd64991b65f86325b74 100644 (file)
@@ -15,6 +15,7 @@
                 "jquery-form": "^4.3.0",
                 "jquery.scrollto": "^2.1.3",
                 "keymaster-reloaded": "^1.7.2",
+                "line-awesome": "^1.3.0",
                 "lz4js": "^0.2.0",
                 "noty": "^3.2.0-beta-deprecated",
                 "pako": "^2.1.0",
                 "node": ">=10"
             }
         },
+        "node_modules/line-awesome": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/line-awesome/-/line-awesome-1.3.0.tgz",
+            "integrity": "sha512-Y0YHksL37ixDsHz+ihCwOtF5jwJgCDxQ3q+zOVgaSW8VugHGTsZZXMacPYZB1/JULBi6BAuTCTek+4ZY/UIwcw=="
+        },
         "node_modules/lines-and-columns": {
             "version": "1.2.4",
             "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
index bd93cc6beb9f35c26b1d91077fa6a68905d13ca3..2c3d43708bd451bfd604bd554fe433ef261781c8 100644 (file)
@@ -37,6 +37,7 @@
         "jquery-form": "^4.3.0",
         "jquery.scrollto": "^2.1.3",
         "keymaster-reloaded": "^1.7.2",
+        "line-awesome": "^1.3.0",
         "lz4js": "^0.2.0",
         "noty": "^3.2.0-beta-deprecated",
         "pako": "^2.1.0",
diff --git a/public/packages/fluidbook/toolbox/css/stats.css b/public/packages/fluidbook/toolbox/css/stats.css
new file mode 100644 (file)
index 0000000..a72fa56
--- /dev/null
@@ -0,0 +1,121 @@
+.chart-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+table.stats-details {
+  width: 100%;
+  table-layout: fixed;
+}
+table.stats-details th,
+table.stats-details td {
+  padding: 0.5em 0.75em;
+}
+table.stats-details td,
+table.stats-details .with-aliases {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+table.stats-details .with-aliases .number {
+  font-size: 75%;
+  display: block;
+  font-style: italic;
+  margin-top: -2px;
+}
+table.stats-details .with-aliases .alias:after {
+  content: ', ';
+}
+table.stats-details .with-aliases .alias:last-of-type:after {
+  content: '';
+}
+table.stats-details thead tr {
+  position: sticky;
+  top: 0;
+  background-color: #fafafa;
+}
+table.stats-details thead th {
+  border-bottom: medium solid rgba(0, 40, 100, 0.12);
+}
+[data-name="formatted_date"] {
+  white-space: nowrap;
+}
+.table-columns {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 2em;
+}
+.table-columns > div {
+  flex: 1;
+}
+.no-statistics {
+  background-color: #fefce9;
+  color: #854e18;
+  padding: 1.5em;
+  margin-top: 1.5em;
+}
+.heading-subtitle {
+  opacity: 0.6;
+}
+.heading-subtitle:before {
+  content: ' — ';
+}
+/*=== Date Range Picker ===*/
+[data-daterangepicker]:hover {
+  color: #467fcf;
+}
+/*=== Table Column Sorter ===*/
+#sorttable_sortfwdind,
+#sorttable_sortrevind {
+  display: none !important;
+}
+.sortable th:not(.sorttable_nosort) {
+  cursor: pointer;
+  white-space: nowrap;
+  position: relative;
+  font-size: 100% !important;
+}
+.sortable th:not(.sorttable_nosort):after,
+.sortable th:not(.sorttable_nosort):before {
+  position: absolute;
+  content: "\f0d7";
+  font-family: Line Awesome Free;
+  font-weight: 900;
+  right: 0.4em;
+  top: 50%;
+  font-size: 14px !important;
+  line-height: 1;
+  opacity: 0.125;
+}
+.sortable th:not(.sorttable_nosort):before {
+  content: "\f0d8";
+  top: auto;
+  bottom: 50%;
+}
+.sortable th:not(.sorttable_nosort).sorttable_sorted_reverse:after {
+  opacity: 1;
+}
+.sortable th:not(.sorttable_nosort).sorttable_sorted:before {
+  opacity: 1;
+}
+.whitespace-nowrap {
+  white-space: nowrap;
+}
+.summary-details,
+.share-details {
+  opacity: 0.6;
+  display: inline-block;
+  margin-left: 0.5em;
+}
+.share-details:before {
+  content: '(';
+}
+.share-details:after {
+  content: ')';
+}
+.share-details span {
+  margin-right: 10px;
+}
+.share-details span:last-of-type {
+  margin-right: 0;
+}
+/*# sourceMappingURL=stats.css.map */
\ No newline at end of file
diff --git a/public/packages/fluidbook/toolbox/css/stats.css.map b/public/packages/fluidbook/toolbox/css/stats.css.map
new file mode 100644 (file)
index 0000000..64ea470
--- /dev/null
@@ -0,0 +1 @@
+{"version":3,"sources":["stats.less"],"names":[],"mappings":"AAAA;EACI,aAAA;EACA,mBAAA;EACA,8BAAA;;AAGJ,KAAK;EACD,WAAA;EACA,mBAAA;;AAFJ,KAAK,cAID;AAJJ,KAAK,cAIG;EACA,qBAAA;;AALR,KAAK,cASD;AATJ,KAAK,cASG;EACA,gBAAA;EACA,uBAAA;;AAXR,KAAK,cAcD,cACI;EACI,cAAA;EACA,cAAA;EACA,kBAAA;EACA,gBAAA;;AAnBZ,KAAK,cAcD,cAQI,OAAM;EACF,SAAS,IAAT;;AAvBZ,KAAK,cAcD,cAYI,OAAM,aAAa;EACf,SAAS,EAAT;;AA3BZ,KAAK,cA+BD,MACI;EACI,gBAAA;EACA,MAAA;EACA,yBAAA;;AAnCZ,KAAK,cA+BD,MAOI;EACI,kDAAA;;AAOZ;EACI,mBAAA;;AAGJ;EACI,aAAA;EACA,eAAA;EACA,QAAA;;AAHJ,cAKI;EACI,OAAA;;AAKR;EACI,yBAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;;AAGJ;EACI,YAAA;;AAEA,iBAAC;EACG,SAAS,KAAT;;;AAKR,sBAAsB;EAClB,cAAA;;;AAIJ;AAAuB;EACnB,wBAAA;;AAGJ,SAAU,GAAE,IAAI;EACZ,eAAA;EACA,mBAAA;EACA,kBAAA;EACA,0BAAA;;AAEA,SANM,GAAE,IAAI,mBAMX;AAAQ,SANH,GAAE,IAAI,mBAMF;EACN,kBAAA;EACA,SAAS,OAAT;EACA,8BAAA;EACA,gBAAA;EACA,YAAA;EACA,QAAA;EACA,0BAAA;EACA,cAAA;EACA,cAAA;;AAGJ,SAlBM,GAAE,IAAI,mBAkBX;EACG,SAAS,OAAT;EACA,SAAA;EACA,WAAA;;AAGJ,SAxBM,GAAE,IAAI,mBAwBX,yBAAyB;EACtB,UAAA;;AAGJ,SA5BM,GAAE,IAAI,mBA4BX,iBAAiB;EACd,UAAA;;AAIR;EACI,mBAAA;;AAIJ;AAAkB;EACd,YAAA;EACA,qBAAA;EACA,kBAAA;;AAIA,cAAC;EACG,SAAS,GAAT;;AAGJ,cAAC;EACG,SAAS,GAAT;;AANR,cASI;EACI,kBAAA;;AACA,cAFJ,KAEK;EACG,eAAA","file":"stats.css"}
\ No newline at end of file
diff --git a/public/packages/fluidbook/toolbox/css/stats.less b/public/packages/fluidbook/toolbox/css/stats.less
new file mode 100644 (file)
index 0000000..f94cc04
--- /dev/null
@@ -0,0 +1,152 @@
+.chart-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
+
+table.stats-details {
+    width: 100%;
+    table-layout: fixed;
+
+    th, td {
+        padding: 0.5em 0.75em;
+    }
+
+
+    td, .with-aliases {
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    .with-aliases {
+        .number {
+            font-size: 75%;
+            display: block;
+            font-style: italic;
+            margin-top: -2px;
+        }
+
+        .alias:after {
+            content: ', ';
+        }
+
+        .alias:last-of-type:after {
+            content: '';
+        }
+    }
+
+    thead {
+        tr {
+            position: sticky;
+            top: 0;
+            background-color: #fafafa;
+        }
+
+        th {
+            border-bottom: medium solid rgba(0, 40, 100, .12);
+        }
+    }
+
+}
+
+
+[data-name="formatted_date"] {
+    white-space: nowrap;
+}
+
+.table-columns {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 2em;
+
+    > div {
+        flex: 1;
+    }
+}
+
+
+.no-statistics {
+    background-color: #fefce9;
+    color: #854e18;
+    padding: 1.5em;
+    margin-top: 1.5em;
+}
+
+.heading-subtitle {
+    opacity: 0.6;
+
+    &:before {
+        content: ' — ';
+    }
+}
+
+/*=== Date Range Picker ===*/
+[data-daterangepicker]:hover {
+    color: #467fcf;
+}
+
+/*=== Table Column Sorter ===*/
+#sorttable_sortfwdind, #sorttable_sortrevind {
+    display: none !important;
+}
+
+.sortable th:not(.sorttable_nosort) {
+    cursor: pointer;
+    white-space: nowrap;
+    position: relative;
+    font-size: 100% !important;
+
+    &:after, &:before {
+        position: absolute;
+        content: "\f0d7";
+        font-family: Line Awesome Free;
+        font-weight: 900;
+        right: .4em;
+        top: 50%;
+        font-size: 14px !important;
+        line-height: 1;
+        opacity: 0.125;
+    }
+
+    &:before {
+        content: "\f0d8";
+        top: auto;
+        bottom: 50%;
+    }
+
+    &.sorttable_sorted_reverse:after {
+        opacity: 1;
+    }
+
+    &.sorttable_sorted:before {
+        opacity: 1;
+    }
+}
+
+.whitespace-nowrap {
+    white-space: nowrap;
+}
+
+
+.summary-details, .share-details {
+    opacity: 0.6;
+    display: inline-block;
+    margin-left: 0.5em;
+}
+
+.share-details {
+    &:before {
+        content: '(';
+    }
+
+    &:after {
+        content: ')';
+    }
+
+    span {
+        margin-right: 10px;
+        &:last-of-type{
+            margin-right: 0;
+        }
+    }
+}
diff --git a/resources/views/fluidbook_stats/loader.blade.php b/resources/views/fluidbook_stats/loader.blade.php
deleted file mode 100644 (file)
index 515658b..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-{{-- __('!! Statistiques') --}}
-@extends(backpack_view('blank'))
-@push('after_scripts')
-    {{-- Include the base libraries for the chart and the date picker here so that they're ready
-    when the report HTML and extra scripts are injected --}}
-
-    {{-- Charting library --}}
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"
-            integrity="sha512-ElRFoEQdI5Ht6kZvyzXhYG9NqjtkmlkfYk0wr6wHxU9JEHakS7UJZNeml5ALk+8IKlU6jDgMabC3vkumRokgJA=="
-            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-    {{-- Date Range picker: https://www.daterangepicker.com/ --}}
-    <script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment-with-locales.min.js"></script>
-    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
-
-    {{-- Simple Table Sorter --}}
-    <script type="text/javascript" src="{{ asset('packages/sorttable/sorttable.js') }}"></script>
-    {{-- This script works on any tables with the "sortable" class. There's no extra setup needed here. --}}
-@endpush
-
-@section('after_styles')
-    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css"/>
-    <style>
-        .chart-header {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-        }
-
-        table.stats-details {
-            width: 100%;
-            table-layout: fixed;
-        }
-
-        table.stats-details th, table.stats-details td {
-            padding: 0.5em 0.75em;
-        }
-
-        table.stats-details td, .with-aliases {
-            overflow: hidden;
-            text-overflow: ellipsis;
-        }
-
-        table.stats-details thead tr {
-            position: sticky;
-            top: 0;
-            background-color: #fafafa;
-        }
-
-        table.stats-details thead th {
-            border-bottom: medium solid rgba(0, 40, 100, .12);
-        }
-
-        [data-name="formatted_date"] {
-            white-space: nowrap;
-        }
-
-        .table-columns {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 2em;
-        }
-
-        .table-columns > div {
-            flex: 1;
-        }
-
-        .no-statistics {
-            background-color: #fefce9;
-            color: #854e18;
-            padding: 1.5em;
-            margin-top: 1.5em;
-        }
-
-        .heading-subtitle {
-            opacity: 0.6;
-        }
-
-        .heading-subtitle:before {
-            content: ' — ';
-        }
-
-        /*=== Date Range Picker ===*/
-        [data-daterangepicker]:hover {
-            color: #467fcf;
-        }
-
-        /*=== Table Column Sorter ===*/
-        #sorttable_sortfwdind, #sorttable_sortrevind {
-            display: none !important;
-        }
-
-        .sortable th:not(.sorttable_nosort) {
-            cursor: pointer;
-            white-space: nowrap;
-            position: relative;
-            font-size: 100% !important;
-        }
-
-        .sortable th:not(.sorttable_nosort):after, .sortable th:not(.sorttable_nosort):before {
-            position: absolute;
-            content: "\f0d7";
-            font-family: Line Awesome Free;
-            font-weight: normal;
-            font-weight: 900;
-            right: .4em;
-            top: 50%;
-            font-size: 14px !important;
-            line-height: 1;
-            opacity: 0.125;
-        }
-
-        .sortable th:not(.sorttable_nosort):before {
-            content: "\f0d8";
-            top: auto;
-            bottom: 50%;
-        }
-
-        .sortable th:not(.sorttable_nosort).sorttable_sorted_reverse:after {
-            opacity: 1;
-        }
-
-        .sortable th:not(.sorttable_nosort).sorttable_sorted:before {
-            opacity: 1;
-        }
-
-        .whitespace-nowrap {
-            white-space: nowrap;
-        }
-
-        .with-aliases .number {
-            font-size: 75%;
-            display: block;
-            font-style: italic;
-            margin-top: -2px;
-        }
-
-        .with-aliases .alias:after {
-            content: ', ';
-        }
-
-        .with-aliases .alias:last-of-type:after {
-            content: '';
-        }
-    </style>
-@endsection
-
-@section('content')
-    <div id="stats_wrapper">
-        @include('fluidbook_stats.summary')
-    </div>
-@endsection
diff --git a/resources/views/fluidbook_stats/stats.blade.php b/resources/views/fluidbook_stats/stats.blade.php
new file mode 100644 (file)
index 0000000..f565bfd
--- /dev/null
@@ -0,0 +1,484 @@
+{{-- __('!! Statistiques') --}}
+@extends(backpack_view('blank'))
+@push('after_scripts')
+    {{-- Include the base libraries for the chart and the date picker here so that they're ready
+    when the report HTML and extra scripts are injected --}}
+
+    {{-- Charting library --}}
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"
+            integrity="sha512-ElRFoEQdI5Ht6kZvyzXhYG9NqjtkmlkfYk0wr6wHxU9JEHakS7UJZNeml5ALk+8IKlU6jDgMabC3vkumRokgJA=="
+            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+
+    {{-- Date Range picker: https://www.daterangepicker.com/ --}}
+    <script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment-with-locales.min.js"></script>
+    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
+
+    {{-- Simple Table Sorter --}}
+    <script type="text/javascript" src="{{ asset('packages/sorttable/sorttable.js') }}"></script>
+    {{-- This script works on any tables with the "sortable" class. There's no extra setup needed here. --}}
+@endpush
+
+@section('after_styles')
+    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css"/>
+    <link rel="stylesheet" type="text/css" href="{{asset('packages/fluidbook/toolbox/css/stats.css')}}">
+@endsection
+
+@section('content')
+    <div id="stats_wrapper">
+        {{-- __('!! Statistiques') --}}
+        {{-- Statistics Report --}}
+        {{-- This doesn't extend any templates because it is fetched from loader.blade.php and injected via JS --}}
+
+        @php
+            $tableClasses='stats-details sortable bg-white table table-striped table-hover nowrap rounded shadow-xs border-xs mt-2 dataTable dtr-inline';
+            $fluidbookBaseURL=$fluidbook->getPreviewURL();
+        @endphp
+
+        <h2 class="mt-4">
+            {{ __('Statistiques') }}
+        </h2>
+
+        <div data-daterangepicker class="mb-4" style="cursor: pointer"
+             title="{{ __('Période').' - '.__('Cliquer pour changer l\'intervalle') }}">
+            <i class="las la-calendar-week align-middle mr-1" style="font-size: 32px;"></i>
+            <span class="date-range-text">
+        {!! $formatted_date_range !!}
+    </span>
+        </div>
+
+
+        <table class="{!! $tableClasses !!}">
+            <thead style="display: none">
+            </thead>
+            <tr>
+                <td>#</td>
+                <td>{{ $fluidbook->id }}</td>
+            </tr>
+            <tr>
+                <td>{{ __('Titre de la publication') }}</td>
+                <td>{{ $title }}</td>
+            </tr>
+            <tr>
+                <td>{{ __('Crée le') }}</td>
+                <td>
+                    {{ $fluidbook->created_at->isoFormat('Do MMMM YYYY') }}
+                    <span class="summary-details">
+            ({{ $fluidbook->created_at->diffForHumans([
+                    'parts' => 2, // How many levels of detail to go into (ie. years, months, days)
+                    'join' => true, // Join string with natural language separators for the locale
+                ])
+            }})
+        </span>
+                </td>
+            </tr>
+            <tr>
+                <td>{{ __('Nombre de pages') }}</td>
+                <td>{{ $page_count }}</td>
+            </tr>
+
+            {{-- Summary of totals --}}
+            @if($period_details->isNotEmpty())
+                @foreach ($table_map['summary'] as $summary_key => $summary_heading)
+                    @if($summary_key === 'formatted_date' || $period_details->sum($summary_key)===0)
+                        @continue
+                    @endif
+                    <tr>
+                        <td>{{ $summary_heading }}</td>
+                        <td>
+                            {{ $formatter->format($period_details->sum($summary_key)) }}
+                            @if($summary_key==='nb_shares' && $period_details->sum($summary_key)>0)
+                                <span class="share-details">
+                                @foreach($shareDetails as $type=>$details)
+                                        <span class="share-details-{{$type}}"><i class="{{$details['icon']}}"></i> {{$details['nb']}}</span>
+                                    @endforeach
+                        </span>
+
+                            @endif
+                        </td>
+                    </tr>
+                @endforeach
+
+                <tr>
+                    <td>{{ __('Recherches') }}</td>
+                    <td>{{ $formatter->format($searches->sum()) }}</td>
+                </tr>
+            @endif
+        </table>
+        <a id="chart"></a>
+        @if($period_details->isNotEmpty())
+
+            {{-- Period (segmentation) override controls [ Day / Week / Month / Year ] --}}
+            <div class="chart-header mt-5">
+
+                <h2>{!! $chart_heading !!}</h2>
+                <div class="btn-group m-t-10">
+                    @foreach($period_map as $period_key => $period_title)
+                        <a class="btn @if($period_key === $period)
+                btn-success
+                @else
+                btn-secondary
+@endif"
+                           href="{{ route('stats', compact('fluidbook_id', 'hash') + ['date' => $date ?? '-', 'period_override' => $period_key]) }}#chart">
+                            {{ $period_title['singular'] }}
+                        </a>
+                    @endforeach
+                </div>
+            </div>
+
+            {{-- Chart --}}
+            <div style="height: 500px">
+                <canvas id="stats_chart"></canvas>
+            </div>
+
+            {{-- Stats for each period entry (year, month, week or day) --}}
+            <table class="{!! $tableClasses !!}">
+                <thead>
+                <tr>
+                    @foreach ($table_map['summary'] as $summary_heading_key => $summary_heading)
+                        <th>
+                            {{ $summary_heading }}
+                        </th>
+                    @endforeach
+                </tr>
+                </thead>
+                <tbody>
+                @foreach($period_details as $date_key => $period_data)
+                    <tr>
+                        @foreach (array_keys($table_map['summary']) as $summary_key)
+                            <td data-name="{{ $summary_key }}"
+                                @if($summary_key === 'formatted_date')sorttable_customkey="{{ $period_data['raw_date'] }}"@endif>
+                                @if(isset($period_data[$summary_key]))
+                                    {!! is_int($period_data[$summary_key]) ? $formatter->format($period_data[$summary_key]) : $period_data[$summary_key] !!}
+                                @else
+                                    -
+                                @endif
+                            </td>
+                        @endforeach
+                    </tr>
+                @endforeach
+                </tbody>
+            </table>
+
+            {{-- Stats segregated by page number --}}
+            <h3 class="mt-5">{{ __('Détails par page') }} <small>({!! $formatted_date_range !!})</small></h3>
+
+            <table class="{!! $tableClasses !!}">
+                <thead>
+                <tr>
+                    @foreach ($table_map['per-page'] as $page_data_heading_key => $page_data_heading)
+                        {{-- In the case of the "page_group" data, we want it to be sorted in ascending order by default, even though it's a numeric column --}}
+                        <th @if($page_data_heading_key === 'page_group')@endif>
+                            {{ $page_data_heading }}
+                        </th>
+                    @endforeach
+                </tr>
+                </thead>
+                <tbody>
+                @foreach($pages as $page_group => $page_data)
+                    <tr>
+                        @foreach (array_keys($table_map['per-page']) as $summary_key)
+                            <td data-name="{{ $summary_key }}"
+                                @if($summary_key === 'page_group')sorttable_customkey="{{ $page_data['page_number'] }}"@endif>
+                                @if($summary_key === 'page_group')
+                                    @if(count($page_data['page_aliases'])>0)
+                                        <div class="with-aliases">
+                                            @foreach($page_data['page_aliases'] as $alias)
+                                                <span class="alias"><a target="_blank"
+                                                                       href="{!! $fluidbookBaseURL !!}#/page/{{\Fluidbook\Tools\Links\AnchorLink::normalizeAnchor($alias)}}">{{$alias}}</a></span>
+                                            @endforeach
+                                            @endif
+                                            <a class="number" target="_blank"
+                                               href="{!! $fluidbookBaseURL !!}#/page/{{ $page_data['page_number'] }}">{{$page_data[$summary_key]}}</a>
+                                            @if(count($page_data['page_aliases'])>0)
+                                        </div>
+                                    @endif
+                                @else
+                                    {!! is_int($page_data[$summary_key]) ? $formatter->format($page_data[$summary_key]) : $page_data[$summary_key] !!}
+                                @endif
+                            </td>
+                        @endforeach
+                    </tr>
+                @endforeach
+                </tbody>
+            </table>
+
+            {{-- Additional stats tables organised into columns (outgoing links, search keywords and countries) --}}
+            {{-- Sometimes there are no stats for certain categories, so the number of columns adapts accordingly --}}
+            <div class="table-columns mt-5">
+
+                {{-- Outgoing Links --}}
+                @if($outlinks->isNotEmpty())
+                    <div>
+                        <h3>{{ __('Liens sortants') }}</h3>
+
+                        <table class="{!! $tableClasses !!}">
+                            <thead>
+                            <tr>
+                                <th>{{ __('URL') }}</th>
+                                <th class="sorttable_sorted_reverse">{{ __('Clics') }}</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            @foreach($outlinks as $link)
+                                <tr>
+                                    <td sorttable_customkey="{{$link['label']}}">{!! \Cubist\Util\Url::linkIfisURL($link['label']) !!}</td>
+                                    <td>{{ $formatter->format($link['nb_events']) }}</td>
+                                </tr>
+                            @endforeach
+                            </tbody>
+                        </table>
+                    </div>
+                @endif
+
+                {{-- Search Keywords --}}
+                @if($searches->isNotEmpty())
+                    <div>
+                        <h3>{{ __('Mots recherchés') }}</h3>
+
+                        <table class="{!! $tableClasses !!}">
+                            <thead>
+                            <tr>
+                                <th>{{ __('Requêtes') }}</th>
+                                <th class="sorttable_sorted_reverse">{{ __('Nombre') }}</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            @foreach($searches as $search_query => $search_count)
+                                <tr>
+                                    <td class="whitespace-nowrap">{{ $search_query }}</td>
+                                    <td>{{ $formatter->format($search_count) }}</td>
+                                </tr>
+                            @endforeach
+                            </tbody>
+                        </table>
+                    </div>
+                @endif
+
+                {{-- Visitor Countries --}}
+                @if($countries->isNotEmpty())
+                    <div>
+                        <h3>{{ __('Origine des visiteurs') }}</h3>
+
+                        <table class="{!! $tableClasses !!}">
+                            <thead>
+                            <tr>
+                                <th>{{ __('Pays') }}</th>
+                                <th class="sorttable_sorted_reverse">{{ __('Nombre de visites') }}</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            @foreach($countries as $country)
+                                <tr>
+                                    <td class="whitespace-nowrap" data-sort-value="{{ $country['label'] }}">
+                                        <img src="{{ $country['flag'] }}" alt="{{ $country['label'] }}"
+                                             style="width: 1.5em; margin-right: 0.75em;">
+                                        {{ $country['label'] }}
+                                    </td>
+                                    <td>{{ $formatter->format($country['nb_visits']) }}</td>
+                                </tr>
+                            @endforeach
+                            </tbody>
+                        </table>
+                    </div>
+                @endif
+
+            </div>
+
+
+            {{-- It's possible for there to be no statistics returned by the API --}}
+        @else
+            <div class="no-statistics">
+                <span style="vertical-align: middle; margin-right: 0.5em;">⚠</span>️
+                {{ __('Aucune visite ne correspond à cette période') }}
+            </div>
+        @endif
+
+        @can('fluidbook-publication:admin')
+            <div style="text-align: right;margin-top: 20px;">
+                <a target="_blank"
+                   href="{{route('statsmatomo',['fluidbook_id'=>$fluidbook->id,'hash'=>$fluidbook->hash,'period'=>'range','date'=>$start_date.','.$end_date])}}">{{__('Voir les données brutes sur Matomo')}}</a>
+                @can('superadmin')
+                    | <a target="_blank"
+                         href="/fluidbook-publication/stats/API/{{$fluidbook->id}}">{{__('API Matomo')}}</a>
+                @endcan
+            </div>
+        @endcan
+
+        @push('after_scripts')
+            {{--================== SCRIPTS ==================--}}
+
+            {{-- Date Range picker setup: https://sensortower.github.io/daterangepicker/docs --}}
+            <script>
+                $(function () {
+                    moment.locale('{{ $locale }}');
+                    @if ($locale === 'en')
+                    moment.updateLocale('{{ $locale }}', {
+                        longDateFormat: {
+                            // Date range picker uses the 'L' format for displaying dates
+                            L: 'DD/MM/YYYY', // We don't like the default, backwards US date format
+                        }
+                    });
+                    @endif
+
+                    let baseURL = '{!! route('stats', compact('fluidbook_id', 'hash') + ['date' => '--range--', 'period_override' => $period]) !!}';
+
+                    let creationDate = moment('{{ $fluidbook->created_at->isoFormat('YYYY-MM-DD') }}');
+                    let now = moment();
+
+                    $('[data-daterangepicker]').daterangepicker({
+                        "locale": {
+                            "format": "DD/MM/YYYY",
+                            "separator": " - ",
+                            "applyLabel": "{{__('Appliquer')}}",
+                            "cancelLabel": "{{__('Annuler')}}",
+                            "fromLabel": "{{__('De')}}",
+                            "toLabel": "{{__('à')}}",
+                            "customRangeLabel": "{{__('Personnalisé')}}",
+                            "weekLabel": "W",
+                            "daysOfWeek": [
+                                "{{__('Lu')}}",
+                                "{{__('Ma')}}",
+                                "{{__('Me')}}",
+                                "{{__('Je')}}",
+                                "{{__('Ve')}}",
+                                "{{__('Sa')}}",
+                                "{{__('Di')}}"
+                            ],
+                            "monthNames": [
+                                "{{__('Janvier')}}",
+                                "{{__('Février')}}",
+                                "{{__('Mars')}}",
+                                "{{__('Avril')}}",
+                                "{{__('Mai')}}",
+                                "{{__('Juin')}}",
+                                "{{__('Juillet')}}",
+                                "{{__('Août')}}",
+                                "{{__('Septembre')}}",
+                                "{{__('Octobre')}}",
+                                "{{__('Novembre')}}",
+                                "{{__('Décembre')}}",
+                            ],
+
+                            "firstDay": 0
+                        },
+                        minDate: creationDate, // Creation date of the Fluidbook
+                        maxDate: now,
+                        startDate: moment('{{ $start_date }}'),
+                        endDate: moment('{{ $end_date }}'),
+                        ranges: {
+                            '{{__('Ce mois')}}': [moment().startOf('month'), now],
+                            '{{__('Les 30 derniers jours')}}': [moment().subtract(29, 'days'), moment()],
+                            '{{__('Cette année')}}': [moment().startOf('year'), now],
+                            '{{__('Depuis la création')}}': [creationDate, now],
+                        },
+                        "alwaysShowCalendars": true,
+                    }, function (start, end) {
+                        $('[data-daterangepicker] span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
+                        window.location = baseURL.replace('--range--', start.format('YYYY-MM-DD') + "," + end.format('YYYY-MM-DD'));
+                    });
+
+                    {{--============================================================================================================--}}
+
+                    {{-- Charting library --}}
+                    //=== Chart Setup
+                    const labels = {!! json_encode(array_keys($tooltip_labels)) !!};
+                    const tooltip_labels = {!! json_encode($tooltip_labels) !!};
+                    const data = {
+                        labels: labels,
+                        datasets: {!! $chart_datasets !!}
+                    };
+
+                    let maxvisits = 0;
+                    $.each(data.datasets[1].data, function (k, v) {
+                        if (v === null) {
+                            return;
+                        }
+                        maxvisits = Math.max(maxvisits, v);
+                    });
+
+                    //=== Plugins
+                    //== Offset Bars Plugin: creates a small offset between stacked bars
+                    const offsetBars = {
+                        id: 'offsetBars',
+                        beforeDatasetsDraw(chart, args, options) {
+
+                            // Create an offset between the stacked bars
+                            chart.config.data.datasets.forEach(function (dataset, datasetIndex) {
+
+                                const bar_offset_percentage = dataset.bar_offset || 0;
+
+                                // Go through each data point (bar) and apply the horizontal positioning offset
+                                chart.getDatasetMeta(datasetIndex).data.forEach(function (dataPoint, index) {
+
+                                    let offset = Math.round(dataPoint.width * bar_offset_percentage / 100);
+
+                                    // Make sure offset amount isn't too small
+                                    if (offset > 0 && offset < 2) {
+                                        offset = 2;
+                                    } else if (offset < 0 && offset > -2) {
+                                        offset = -2;
+                                    }
+
+                                    dataPoint.x = chart.scales.x.getPixelForValue(index) + offset;
+                                });
+                            });
+                        }
+                    };
+
+                    //=== Chart Configuration
+                    const config = {
+                        type: 'bar',
+                        data: data,
+                        options: {
+                            responsive: true,
+                            maintainAspectRatio: false,
+                            maxBarThickness: 20, // Prevent bars being ridiculously wide when there isn't much data
+                            scales: {
+                                x: {stacked: true},
+                                y: {
+                                    type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+                                    position: 'left',
+                                    stacked: false,
+                                },
+                                y2: {
+                                    type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+                                    position: 'right',
+                                    reverse: false,
+                                    ticks: {
+                                        color: '#f54d00',
+                                    },
+                                    grid: {
+                                        drawOnChartArea: false // only want the grid lines for one axis to show up
+                                    },
+                                    max: maxvisits * 3,
+                                    stacked: false,
+                                },
+                            },
+                            plugins: {
+                                tooltip: {
+                                    mode: 'index',
+                                    position: 'nearest',
+                                    callbacks: {
+                                        title: function (context) {
+                                            return tooltip_labels[context[0].label];
+                                        }
+                                    },
+                                }
+                            }
+                        },
+                        plugins: [offsetBars]
+                    };
+
+                    //=== Render Chart
+                    const statsChart = new Chart(
+                        document.getElementById('stats_chart'),
+                        config
+                    );
+                });
+
+
+            </script>
+        @endpush
+
+    </div>
+@endsection
diff --git a/resources/views/fluidbook_stats/summary.blade.php b/resources/views/fluidbook_stats/summary.blade.php
deleted file mode 100644 (file)
index 2e70562..0000000
+++ /dev/null
@@ -1,443 +0,0 @@
-{{-- __('!! Statistiques') --}}
-{{-- Statistics Report --}}
-{{-- This doesn't extend any templates because it is fetched from loader.blade.php and injected via JS --}}
-
-@php
-    $tableClasses='stats-details sortable bg-white table table-striped table-hover nowrap rounded shadow-xs border-xs mt-2 dataTable dtr-inline';
-    $fluidbookBaseURL=$fluidbook->getPreviewURL();
-@endphp
-
-<h2 class="mt-4">
-    {{ __('Statistiques') }}
-</h2>
-
-<div data-daterangepicker class="mb-4" style="cursor: pointer"
-     title="{{ __('Période').' - '.__('Cliquer pour changer l\'intervalle') }}">
-    <i class="las la-calendar-week align-middle mr-1" style="font-size: 32px;"></i>
-    <span class="date-range-text">
-        {!! $formatted_date_range !!}
-    </span>
-</div>
-
-
-<table class="{!! $tableClasses !!}">
-    <thead style="display: none">
-    </thead>
-    <tr>
-        <td>#</td>
-        <td>{{ $fluidbook->id }}</td>
-    </tr>
-    <tr>
-        <td>{{ __('Titre de la publication') }}</td>
-        <td>{{ $title }}</td>
-    </tr>
-    <tr>
-        <td>{{ __('Crée le') }}</td>
-        <td>
-            {{ $fluidbook->created_at->isoFormat('Do MMMM YYYY') }}
-            <span style="opacity: 0.6; display: inline-block; margin-left: 0.5em;">
-            ({{ $fluidbook->created_at->diffForHumans([
-                    'parts' => 2, // How many levels of detail to go into (ie. years, months, days)
-                    'join' => true, // Join string with natural language separators for the locale
-                ])
-            }})
-        </span>
-        </td>
-    </tr>
-    <tr>
-        <td>{{ __('Nombre de pages') }}</td>
-        <td>{{ $page_count }}</td>
-    </tr>
-
-    {{-- Summary of totals --}}
-    @if($period_details->isNotEmpty())
-        @foreach ($table_map['summary'] as $summary_key => $summary_heading)
-            @php
-                if ($summary_key === 'formatted_date') continue;
-            @endphp
-            <tr>
-                <td>{{ $summary_heading }}</td>
-                <td>{{ $formatter->format($period_details->sum($summary_key)) }}</td>
-            </tr>
-        @endforeach
-
-        <tr>
-            <td>{{ __('Recherches') }}</td>
-            <td>{{ $formatter->format($searches->sum()) }}</td>
-        </tr>
-    @endif
-</table>
-<a id="chart"></a>
-@if($period_details->isNotEmpty())
-
-    {{-- Period (segmentation) override controls [ Day / Week / Month / Year ] --}}
-    <div class="chart-header mt-5">
-
-        <h2>{!! $chart_heading !!}</h2>
-        <div class="btn-group m-t-10">
-            @foreach($period_map as $period_key => $period_title)
-                <a class="btn @if($period_key === $period)
-                btn-success
-                @else
-                btn-secondary
-@endif"
-                   href="{{ route('stats', compact('fluidbook_id', 'hash') + ['date' => $date ?? '-', 'period_override' => $period_key]) }}#chart">
-                    {{ $period_title['singular'] }}
-                </a>
-            @endforeach
-        </div>
-    </div>
-
-    {{-- Chart --}}
-    <div style="height: 500px">
-        <canvas id="stats_chart"></canvas>
-    </div>
-
-    {{-- Stats for each period entry (year, month, week or day) --}}
-    <table class="{!! $tableClasses !!}">
-        <thead>
-        <tr>
-            @foreach ($table_map['summary'] as $summary_heading_key => $summary_heading)
-                <th>
-                    {{ $summary_heading }}
-                </th>
-            @endforeach
-        </tr>
-        </thead>
-        <tbody>
-        @foreach($period_details as $date_key => $period_data)
-            <tr>
-                @foreach (array_keys($table_map['summary']) as $summary_key)
-                    <td data-name="{{ $summary_key }}"
-                        @if($summary_key === 'formatted_date')sorttable_customkey="{{ $period_data['raw_date'] }}"@endif>
-                        @if(isset($period_data[$summary_key]))
-                            {!! is_int($period_data[$summary_key]) ? $formatter->format($period_data[$summary_key]) : $period_data[$summary_key] !!}
-                        @else
-                            -
-                        @endif
-                    </td>
-                @endforeach
-            </tr>
-        @endforeach
-        </tbody>
-    </table>
-
-    {{-- Stats segregated by page number --}}
-    <h3 class="mt-5">{{ __('Détails par page') }} <small>({!! $formatted_date_range !!})</small></h3>
-
-    <table class="{!! $tableClasses !!}">
-        <thead>
-        <tr>
-            @foreach ($table_map['per-page'] as $page_data_heading_key => $page_data_heading)
-                {{-- In the case of the "page_group" data, we want it to be sorted in ascending order by default, even though it's a numeric column --}}
-                <th @if($page_data_heading_key === 'page_group')@endif>
-                    {{ $page_data_heading }}
-                </th>
-            @endforeach
-        </tr>
-        </thead>
-        <tbody>
-        @foreach($pages as $page_group => $page_data)
-            <tr>
-                @foreach (array_keys($table_map['per-page']) as $summary_key)
-                    <td data-name="{{ $summary_key }}"
-                        @if($summary_key === 'page_group')sorttable_customkey="{{ $page_data['page_number'] }}"@endif>
-                        @if($summary_key === 'page_group')
-                            @if(count($page_data['page_aliases'])>0)
-                                <div class="with-aliases">
-                                    @foreach($page_data['page_aliases'] as $alias)
-                                        <span class="alias"><a target="_blank"
-                                           href="{!! $fluidbookBaseURL !!}#/page/{{\Fluidbook\Tools\Links\AnchorLink::normalizeAnchor($alias)}}">{{$alias}}</a></span>
-                                    @endforeach
-                                    @endif
-                                    <a class="number" target="_blank"
-                                       href="{!! $fluidbookBaseURL !!}#/page/{{ $page_data['page_number'] }}">{{$page_data[$summary_key]}}</a>
-                                    @if(count($page_data['page_aliases'])>0)
-                                </div>
-                            @endif
-                        @else
-                            {!! is_int($page_data[$summary_key]) ? $formatter->format($page_data[$summary_key]) : $page_data[$summary_key] !!}
-                        @endif
-                    </td>
-                @endforeach
-            </tr>
-        @endforeach
-        </tbody>
-    </table>
-
-    {{-- Additional stats tables organised into columns (outgoing links, search keywords and countries) --}}
-    {{-- Sometimes there are no stats for certain categories, so the number of columns adapts accordingly --}}
-    <div class="table-columns mt-5">
-
-        {{-- Outgoing Links --}}
-        @if($outlinks->isNotEmpty())
-            <div>
-                <h3>{{ __('Liens sortants') }}</h3>
-
-                <table class="{!! $tableClasses !!}">
-                    <thead>
-                    <tr>
-                        <th>{{ __('URL') }}</th>
-                        <th class="sorttable_sorted_reverse">{{ __('Clics') }}</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    @foreach($outlinks as $link)
-                        <tr>
-                            <td sorttable_customkey="{{$link['label']}}">{!! \Cubist\Util\Url::linkIfisURL($link['label']) !!}</td>
-                            <td>{{ $formatter->format($link['nb_events']) }}</td>
-                        </tr>
-                    @endforeach
-                    </tbody>
-                </table>
-            </div>
-        @endif
-
-        {{-- Search Keywords --}}
-        @if($searches->isNotEmpty())
-            <div>
-                <h3>{{ __('Mots recherchés') }}</h3>
-
-                <table class="{!! $tableClasses !!}">
-                    <thead>
-                    <tr>
-                        <th>{{ __('Requêtes') }}</th>
-                        <th class="sorttable_sorted_reverse">{{ __('Nombre') }}</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    @foreach($searches as $search_query => $search_count)
-                        <tr>
-                            <td class="whitespace-nowrap">{{ $search_query }}</td>
-                            <td>{{ $formatter->format($search_count) }}</td>
-                        </tr>
-                    @endforeach
-                    </tbody>
-                </table>
-            </div>
-        @endif
-
-        {{-- Visitor Countries --}}
-        @if($countries->isNotEmpty())
-            <div>
-                <h3>{{ __('Origine des visiteurs') }}</h3>
-
-                <table class="{!! $tableClasses !!}">
-                    <thead>
-                    <tr>
-                        <th>{{ __('Pays') }}</th>
-                        <th class="sorttable_sorted_reverse">{{ __('Nombre de visites') }}</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    @foreach($countries as $country)
-                        <tr>
-                            <td class="whitespace-nowrap" data-sort-value="{{ $country['label'] }}">
-                                <img src="{{ $country['flag'] }}" alt="{{ $country['label'] }}"
-                                     style="width: 1.5em; margin-right: 0.75em;">
-                                {{ $country['label'] }}
-                            </td>
-                            <td>{{ $formatter->format($country['nb_visits']) }}</td>
-                        </tr>
-                    @endforeach
-                    </tbody>
-                </table>
-            </div>
-        @endif
-
-    </div>
-
-
-    {{-- It's possible for there to be no statistics returned by the API --}}
-@else
-    <div class="no-statistics">
-        <span style="vertical-align: middle; margin-right: 0.5em;">⚠</span>️
-        {{ __('Aucune visite ne correspond à cette période') }}
-    </div>
-@endif
-
-@can('fluidbook-publication:admin')
-    <div style="text-align: right;margin-top: 20px;">
-        <a target="_blank"
-           href="{{route('statsmatomo',['fluidbook_id'=>$fluidbook->id,'hash'=>$fluidbook->hash,'period'=>'range','date'=>$start_date.','.$end_date])}}">{{__('Voir les données brutes sur Matomo')}}</a>
-        @can('superadmin')
-            | <a target="_blank" href="/fluidbook-publication/stats/API/{{$fluidbook->id}}">{{__('API Matomo')}}</a>
-        @endcan
-    </div>
-@endcan
-
-@push('after_scripts')
-    {{--================== SCRIPTS ==================--}}
-
-    {{-- Date Range picker setup: https://sensortower.github.io/daterangepicker/docs --}}
-    <script>
-        $(function () {
-            moment.locale('{{ $locale }}');
-            @if ($locale === 'en')
-            moment.updateLocale('{{ $locale }}', {
-                longDateFormat: {
-                    // Date range picker uses the 'L' format for displaying dates
-                    L: 'DD/MM/YYYY', // We don't like the default, backwards US date format
-                }
-            });
-            @endif
-
-            let baseURL = '{!! route('stats', compact('fluidbook_id', 'hash') + ['date' => '--range--', 'period_override' => $period]) !!}';
-
-            let creationDate = moment('{{ $fluidbook->created_at->isoFormat('YYYY-MM-DD') }}');
-            let now = moment();
-
-            $('[data-daterangepicker]').daterangepicker({
-                "locale": {
-                    "format": "DD/MM/YYYY",
-                    "separator": " - ",
-                    "applyLabel": "{{__('Appliquer')}}",
-                    "cancelLabel": "{{__('Annuler')}}",
-                    "fromLabel": "{{__('De')}}",
-                    "toLabel": "{{__('à')}}",
-                    "customRangeLabel": "{{__('Personnalisé')}}",
-                    "weekLabel": "W",
-                    "daysOfWeek": [
-                        "{{__('Lu')}}",
-                        "{{__('Ma')}}",
-                        "{{__('Me')}}",
-                        "{{__('Je')}}",
-                        "{{__('Ve')}}",
-                        "{{__('Sa')}}",
-                        "{{__('Di')}}"
-                    ],
-                    "monthNames": [
-                        "{{__('Janvier')}}",
-                        "{{__('Février')}}",
-                        "{{__('Mars')}}",
-                        "{{__('Avril')}}",
-                        "{{__('Mai')}}",
-                        "{{__('Juin')}}",
-                        "{{__('Juillet')}}",
-                        "{{__('Août')}}",
-                        "{{__('Septembre')}}",
-                        "{{__('Octobre')}}",
-                        "{{__('Novembre')}}",
-                        "{{__('Décembre')}}",
-                    ],
-
-                    "firstDay": 0
-                },
-                minDate: creationDate, // Creation date of the Fluidbook
-                maxDate: now,
-                startDate: moment('{{ $start_date }}'),
-                endDate: moment('{{ $end_date }}'),
-                ranges: {
-                    '{{__('Ce mois')}}': [moment().startOf('month'), now],
-                    '{{__('Les 30 derniers jours')}}': [moment().subtract(29, 'days'), moment()],
-                    '{{__('Cette année')}}': [moment().startOf('year'), now],
-                    '{{__('Depuis la création')}}': [creationDate, now],
-                },
-                "alwaysShowCalendars": true,
-            }, function (start, end) {
-                $('[data-daterangepicker] span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
-                window.location = baseURL.replace('--range--', start.format('YYYY-MM-DD') + "," + end.format('YYYY-MM-DD'));
-            });
-
-            {{--============================================================================================================--}}
-
-            {{-- Charting library --}}
-            //=== Chart Setup
-            const labels = {!! json_encode(array_keys($tooltip_labels)) !!};
-            const tooltip_labels = {!! json_encode($tooltip_labels) !!};
-            const data = {
-                labels: labels,
-                datasets: {!! $chart_datasets !!}
-            };
-
-            let maxvisits = 0;
-            $.each(data.datasets[1].data, function (k, v) {
-                if (v === null) {
-                    return;
-                }
-                maxvisits = Math.max(maxvisits, v);
-            });
-
-            //=== Plugins
-            //== Offset Bars Plugin: creates a small offset between stacked bars
-            const offsetBars = {
-                id: 'offsetBars',
-                beforeDatasetsDraw(chart, args, options) {
-
-                    // Create an offset between the stacked bars
-                    chart.config.data.datasets.forEach(function (dataset, datasetIndex) {
-
-                        const bar_offset_percentage = dataset.bar_offset || 0;
-
-                        // Go through each data point (bar) and apply the horizontal positioning offset
-                        chart.getDatasetMeta(datasetIndex).data.forEach(function (dataPoint, index) {
-
-                            let offset = Math.round(dataPoint.width * bar_offset_percentage / 100);
-
-                            // Make sure offset amount isn't too small
-                            if (offset > 0 && offset < 2) {
-                                offset = 2;
-                            } else if (offset < 0 && offset > -2) {
-                                offset = -2;
-                            }
-
-                            dataPoint.x = chart.scales.x.getPixelForValue(index) + offset;
-                        });
-                    });
-                }
-            };
-
-            //=== Chart Configuration
-            const config = {
-                type: 'bar',
-                data: data,
-                options: {
-                    responsive: true,
-                    maintainAspectRatio: false,
-                    maxBarThickness: 20, // Prevent bars being ridiculously wide when there isn't much data
-                    scales: {
-                        x: {stacked: true},
-                        y: {
-                            type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
-                            position: 'left',
-                            stacked: false,
-                        },
-                        y2: {
-                            type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
-                            position: 'right',
-                            reverse: false,
-                            ticks: {
-                                color: '#f54d00',
-                            },
-                            grid: {
-                                drawOnChartArea: false // only want the grid lines for one axis to show up
-                            },
-                            max: maxvisits * 3,
-                            stacked: false,
-                        },
-                    },
-                    plugins: {
-                        tooltip: {
-                            mode: 'index',
-                            position: 'nearest',
-                            callbacks: {
-                                title: function (context) {
-                                    return tooltip_labels[context[0].label];
-                                }
-                            },
-                        }
-                    }
-                },
-                plugins: [offsetBars]
-            };
-
-            //=== Render Chart
-            const statsChart = new Chart(
-                document.getElementById('stats_chart'),
-                config
-            );
-        });
-
-
-    </script>
-@endpush