// Translatable list of labels for the available periods
$period_map = [
- 'day' => __('Day'),
- 'week' => __('Week'),
- 'month' => __('Month'),
- 'year' => __('Year'),
+ 'day' => [
+ 'singular' => __('Day'),
+ 'periodic' => __('Daily'),
+ ],
+ 'week' => [
+ 'singular' => __('Week'),
+ 'periodic' => __('Weekly'),
+ ],
+ 'month' => [
+ 'singular' => __('Month'),
+ 'periodic' => __('Monthly'),
+ ],
+ 'year' => [
+ 'singular' => __('Year'),
+ 'periodic' => __('Annual'),
+ ],
];
$dates = $date ? $this->parseDate($date) : false;
$period_override = in_array($period_override, array_keys($period_map)) ? $period_override : null;
$period = $period_override ?? $this->determinePeriod($dates, $fluidbook);
+ $chart_heading = sprintf(__('%s Details'), $period_map[$period]['periodic']);
$report_timespan = '';
// Which mode are we in?
$date_range = "{$start_date},{$end_date}";
$formatted_date_range = Carbon::parse($start_date)->isoFormat('MMMM Do, YYYY') . ' — ' .
Carbon::parse($end_date)->isoFormat('MMMM Do, YYYY');
- $chart_heading = __('Daily Details');
// Human-friendly representation of the time span
// Since our start and end dates are only in the format YYYY-MM-DD, the time defaults to midnight
$start_date = "{$year}-{$month}-01";
$date_range = "{$start_date},{$year}-{$month}-{$last_day_of_month}";
$end_date = ($month == date('m') && $year == date('Y')) ? date('Y-m-d') : "{$year}-{$month}-{$last_day_of_month}";
- $chart_heading = __('Daily Details') .
- '<span class="heading-subtitle">' . Carbon::parse($start_date)->isoFormat('MMMM YYYY') . '</span>';
+ $chart_heading .= '<span class="heading-subtitle">' . Carbon::parse($start_date)->isoFormat('MMMM YYYY') . '</span>';
$formatted_date_range = Carbon::parse($start_date)->isoFormat('Do') . ' — ' .
Carbon::parse($end_date)->isoFormat('Do MMMM, YYYY');
$end_date = $year == date('Y') ? date('Y-m-d') : "{$year}-12-31"; // If it's the current year, don't count future dates
$formatted_date_range = Carbon::parse($start_date)->isoFormat('MMMM Do, YYYY') . ' — ' .
Carbon::parse($end_date)->isoFormat('MMMM Do, YYYY');
- $chart_heading = __('Annual Details') . "<span class='heading-subtitle'>$year</span>";
+ $chart_heading .= "<span class='heading-subtitle'>$year</span>";
} else { // No valid dates specified, display the full data set
$mode = 'overview';
$table_map = [
// Main summary table
'summary' => [
- 'formatted_date' => $period_map[$period],
+ 'formatted_date' => $period_map[$period]['singular'],
'nb_uniq_visitors' => __('Unique Visitors'),
'nb_visits' => __('Visits'),
'nb_hits' => __('Pages Viewed'),
-ms-flex-pack: start;
justify-content: flex-start;
border-radius: 4px;
- padding: 4px;
+ padding: 0.5em 0.25em;
font-size: 13px;
font-family: sans-serif;
line-height: 1.5em;
.daterangepicker .ranges li {
border-radius: 4px;
- margin-bottom: 8px;
text-align: left;
}
+.daterangepicker .ranges li + li {
+ margin-top: 0.5em;
+}
.daterangepicker .custom-range-inputs {
- display: -ms-flexbox;
display: flex;
- margin: -3px;
- margin-bottom: 5px;
+ align-items: center;
+ gap: 0.5em;
+ margin-top: 0.5em;
}
.daterangepicker .custom-range-inputs input {
- min-width: 50px;
- width: 50px;
- -ms-flex: 1;
- flex: 1;
- margin: 3px;
+ min-width: 11ch;
+ flex: 1;
border-radius: 4px;
border: 1px solid #ccc;
height: auto;
}
.daterangepicker .custom-range-buttons {
- display: -ms-flexbox;
display: flex;
- margin: -3px;
+ gap: 0.5em;
+ margin-top: 1em;
}
.daterangepicker .custom-range-buttons button {
- margin: 0;
padding: 4px 9px;
- margin: 3px;
border-radius: 4px;
background: #f5f5f5;
color: #08c;
@section('after_styles')
<link rel="stylesheet" href="{{ asset('packages/daterangepicker/daterangepicker.css') }}">
<style>
+ .container-fluid.animated.fadeIn {
+ {{-- Disable entrance animation on this page because we already have the stats loader --}}
+ animation: none;
+ }
+
@keyframes spinner {
to {
transform: rotate(360deg);
margin: 5em auto;
}
+ .chart-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
.periods {
display: inline-flex;
margin-left: 2em;
}
.periods > * {
- padding: 0.2em 0.8em;
+ padding: 0.35em 0.7em;
border: 1px solid #ddd;
+ line-height: 1;
}
.periods > * + * {
border-left: 0;
width: 100%;
}
table.stats-details th, table.stats-details td {
- padding: 0.5em 1em;
+ padding: 0.5em 0.75em;
}
table.stats-details thead tr {
position: sticky;
background-color: rgba(156, 195, 34, 0.15);
}
+ [data-name="formatted_date"] {
+ white-space: nowrap;
+ }
+
.table-columns {
display: flex;
flex-wrap: wrap;
[data-daterangepicker]:hover {
color: #467fcf;
}
+
+ .daterangepicker.expanded {
+ padding-bottom: 0.75em;
+ }
+
.daterangepicker .periods li:hover, .daterangepicker .periods li.active, .daterangepicker .ranges li:hover, .daterangepicker .ranges li.active {
background: #263340;
}
.daterangepicker .periods li, .daterangepicker .ranges li {
color: #467fcf;
}
+
+ .daterangepicker .ranges {
+ margin-bottom: 1em;
+ }
+
+ .daterangepicker .custom-range-inputs input {
+ padding: 0.25em 0.5em;
+ width: 11ch;
+ }
+ .daterangepicker .custom-range-inputs .arrow-right {
+ opacity: 0.5;
+ }
+ .daterangepicker .custom-range-buttons {
+ flex-direction: row-reverse;
+ }
+ .daterangepicker.expanded .custom-range-buttons {
+ position: absolute;
+ right: 1em;
+ bottom: 0.75em;
+ }
.daterangepicker .custom-range-buttons button {
color: #467fcf;
}
background: #263340;
}
+ .daterangepicker select[name="segmentation"] {
+ border: 1px solid #ccc;
+ border-radius: 0.25em;
+ padding: 0.25em;
+ text-align: left;
+ color: #333;
+ margin: 0;
+ }
+
/*=== Table Column Sorter ===*/
.sortable th:not(.sorttable_nosort) {
cursor: pointer;
showStatsError();
});
-
function hideStatsLoader() {
document.getElementById('stats_loader').style.display = 'none';
}
+ function showStatsLoader() {
+ document.getElementById('stats_wrapper').style.display = 'none';
+ document.getElementById('stats_loader').style.display = 'flex';
+ }
+
function showStatsError() {
hideStatsLoader();
document.getElementById('stats_error').style.display = 'block';
<span class="heading-subtitle">{{ $fluidbook->name }}</span>
</h2>
-<span data-daterangepicker class="mb-2" style="cursor: pointer" title="{{ __('Statistics Period - click to choose a new date range') }}">
+<div data-daterangepicker class="mb-4" style="cursor: pointer" title="{{ __('Statistics Period - click to choose a new date range') }}">
<i class="las la-calendar-week align-middle mr-1" style="font-size: 32px;"></i>
<span class="date-range-text">
{!! $formatted_date_range !!}
<span style="opacity: 0.6; display: inline-block; margin-left: 0.5em;">({{ $report_timespan }})</span>
@endif
</span>
-</span>
-
-<div class="periods mb-4">
- @foreach($period_map as $period_key => $period_title)
- @if($period_key === $period)
- <span>{{ $period_title }}</span>
- @else
- <a href="{{ route('stats', compact('fluidbook_id', 'hash') + ['date' => $date ?? '-', 'period_override' => $period_key]) }}">
- {{ $period_title }}
- </a>
- @endif
- @endforeach
</div>
@if($period_details->isNotEmpty())
- <h2 class="mt-5">{!! $chart_heading !!}</h2>
+ <div class="chart-header mt-5">
+ <h2>{!! $chart_heading !!}</h2>
+ <div class="periods">
+ @foreach($period_map as $period_key => $period_title)
+ @if($period_key === $period)
+ <span>{{ $period_title['singular'] }}</span>
+ @else
+ <a href="{{ route('stats', compact('fluidbook_id', 'hash') + ['date' => $date ?? '-', 'period_override' => $period_key]) }}" onclick="showStatsLoader()">
+ {{ $period_title['singular'] }}
+ </a>
+ @endif
+ @endforeach
+ </div>
+ </div>
{{-- Chart --}}
<canvas id="stats_chart"></canvas>
$('[data-daterangepicker]').daterangepicker({
callback: function(startDate, endDate, period) {
+ showStatsLoader();
let range = startDate.format('YYYY-MM-DD') + ',' + endDate.format('YYYY-MM-DD');
- location.href = `{{ $base_URL }}/${range}`;
-
- // TODO: add loading spinner after changing the date so that we know something is happening while the next page loads...
-
+ let segmentation = document.querySelector('.daterangepicker [name="segmentation"]')?.value || false;
+ let periodOverride = segmentation ? `/${segmentation}` : '';
+ location.href = `{{ $base_URL }}/${range}${periodOverride}`;
},
forceUpdate: false,
minDate: '{{ $fluidbook->created_at->isoFormat('YYYY-MM-DD') }}', // Creation date of the Fluidbook
--}}
});
+ {{-- Inject the segmentation (period) dropdown into the date picker --}}
+ let picker = document.querySelector('.daterangepicker');
+
+ let segmentOptions = '<option value="">{{ __('Segmentation:') }} {{ __('Automatic') }}</option>';
+ @foreach ($period_map as $period_key => $period_title)
+ segmentOptions += '<option value="{{ $period_key }}">{{ __('Segmentation:') }} {{ $period_title['periodic'] }}</option>'
+ @endforeach
+
+ let segmentation = document.createElement('select');
+ segmentation.name = 'segmentation';
+ segmentation.innerHTML = segmentOptions;
+ picker.querySelector('form').prepend(segmentation);
+
+ // Add arrow between range inputs
+ let rangeInputs = picker.querySelector('.custom-range-inputs');
+ let arrow = document.createElement('span');
+ arrow.className = 'arrow-right';
+ rangeInputs.insertBefore(arrow, rangeInputs.children[1]); // Insert in 2nd position
+
+
</script>
{{--============================================================================================================--}}