Files
itflow/js/date_filter.js

184 lines
7.0 KiB
JavaScript

/**
* Date range filter.
*
* Rebuilt on Flatpickr, replacing daterangepicker (jQuery) and moment.
*
* Flatpickr has no preset-ranges feature, so the nine presets daterangepicker
* gave us for free are built here as a panel injected into the calendar via
* the onReady hook. Behaviour is unchanged: picking a preset writes its canned
* value and clears dtf/dtt; picking a custom range writes 'custom' plus both
* dates; either way the form auto-submits.
*
* Date maths is plain Date - weeks start Monday, matching the old isoWeek.
*/
(function () {
'use strict';
var ALL_TIME_START = '1970-01-01';
var ALL_TIME_END = '2099-12-31';
function ymd(d) {
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
return d.getFullYear() + '-' + m + '-' + day;
}
function parseYmd(s) {
var parts = String(s || '').split('-');
if (parts.length !== 3) {
return null;
}
var d = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
return isNaN(d.getTime()) ? null : d;
}
function addDays(d, n) {
var c = new Date(d.getTime());
c.setDate(c.getDate() + n);
return c;
}
/** Monday-based start of week, matching moment's isoWeek. */
function startOfWeek(d) {
var c = new Date(d.getTime());
var dow = (c.getDay() + 6) % 7;
return addDays(c, -dow);
}
function buildRanges() {
var now = new Date();
var lastWeekStart = addDays(startOfWeek(now), -7);
return [
{ label: 'Today', canned: 'today', start: now, end: now },
{ label: 'Yesterday', canned: 'yesterday',
start: addDays(now, -1), end: addDays(now, -1) },
{ label: 'This Week', canned: 'thisweek',
start: startOfWeek(now), end: now },
{ label: 'Last Week', canned: 'lastweek',
start: lastWeekStart, end: addDays(lastWeekStart, 6) },
{ label: 'This Month', canned: 'thismonth',
start: new Date(now.getFullYear(), now.getMonth(), 1), end: now },
{ label: 'Last Month', canned: 'lastmonth',
start: new Date(now.getFullYear(), now.getMonth() - 1, 1),
end: new Date(now.getFullYear(), now.getMonth(), 0) },
{ label: 'This Year', canned: 'thisyear',
start: new Date(now.getFullYear(), 0, 1), end: now },
{ label: 'Last Year', canned: 'lastyear',
start: new Date(now.getFullYear() - 1, 0, 1),
end: new Date(now.getFullYear() - 1, 11, 31) },
{ label: 'All Time', canned: 'alltime',
start: parseYmd(ALL_TIME_START), end: parseYmd(ALL_TIME_END) }
];
}
document.addEventListener('DOMContentLoaded', function () {
var input = document.getElementById('dateFilter');
if (!input || input._flatpickr) {
return;
}
var canned = document.getElementById('canned_date');
var dtf = document.getElementById('dtf');
var dtt = document.getElementById('dtt');
if (!canned || !dtf || !dtt) {
return;
}
// Default to All Time when the page arrives with nothing set
if (!((dtf.value && dtt.value) || canned.value)) {
canned.value = 'alltime';
dtf.value = ALL_TIME_START;
dtt.value = ALL_TIME_END;
}
var ranges = buildRanges();
var submitting = false;
var presetClicked = false;
function setDisplay(start, end, label) {
if (label === 'All Time' ||
(ymd(start) === ALL_TIME_START && ymd(end) === ALL_TIME_END)) {
input.value = 'All Time';
} else {
input.value = ymd(start) + ' \u2014 ' + ymd(end);
}
}
function apply(start, end, range) {
if (submitting) {
return;
}
submitting = true;
if (range) {
canned.value = range.canned;
dtf.value = '';
dtt.value = '';
} else {
canned.value = 'custom';
dtf.value = ymd(start);
dtt.value = ymd(end);
}
setDisplay(start, end, range ? range.label : undefined);
if (input.form) {
input.form.submit();
}
}
var initialStart = parseYmd(dtf.value) || parseYmd(ALL_TIME_START);
var initialEnd = parseYmd(dtt.value) || parseYmd(ALL_TIME_END);
var initialKey = ymd(initialStart) + '|' + ymd(initialEnd);
flatpickr(input, {
mode: 'range',
dateFormat: 'Y-m-d',
defaultDate: [initialStart, initialEnd],
locale: { firstDayOfWeek: 1 },
allowInput: false,
onReady: function (selectedDates, dateStr, instance) {
// The preset panel daterangepicker used to provide
var panel = document.createElement('div');
panel.className = 'itflow-fp-ranges';
ranges.forEach(function (r) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'itflow-fp-range';
btn.textContent = r.label;
btn.addEventListener('click', function () {
// close() fires onClose synchronously - flag it first
// or onClose applies the stale range as a custom one
presetClicked = true;
instance.close();
apply(r.start, r.end, r);
});
panel.appendChild(btn);
});
instance.calendarContainer.classList.add('itflow-fp-with-ranges');
instance.calendarContainer.appendChild(panel);
},
onClose: function (selectedDates, dateStr, instance) {
// A preset closed the calendar itself; its handler applies.
if (presetClicked) {
presetClicked = false;
return;
}
// Abandoned half-selection - put the box back how it was.
if (selectedDates.length !== 2) {
instance.setDate([initialStart, initialEnd], false);
setDisplay(initialStart, initialEnd);
return;
}
// Dismissed without actually changing the range - daterangepicker
// only applied on its Apply button, so don't resubmit here.
if (ymd(selectedDates[0]) + '|' + ymd(selectedDates[1]) === initialKey) {
setDisplay(initialStart, initialEnd);
return;
}
apply(selectedDates[0], selectedDates[1], null);
}
});
setDisplay(initialStart, initialEnd);
});
})();