mirror of
https://github.com/itflow-org/itflow
synced 2026-08-24 16:35:15 +00:00
200 lines
7.9 KiB
JavaScript
200 lines
7.9 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);
|
|
|
|
// 1970-01-01 / 2099-12-31 are SQL sentinels for "no bound", not dates a
|
|
// user picked - and they are also the server's fallback, so most pages
|
|
// arrive in this state. Seeding the calendar with them opened it on
|
|
// January 1970 every time. Treat it as "nothing selected" instead: the
|
|
// calendar opens on the current month with no range highlighted, so the
|
|
// first click starts a fresh selection.
|
|
var allTimeActive = initialKey === ALL_TIME_START + '|' + ALL_TIME_END;
|
|
|
|
flatpickr(input, {
|
|
mode: 'range',
|
|
dateFormat: 'Y-m-d',
|
|
defaultDate: allTimeActive ? null : [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;
|
|
if (r.canned === canned.value ||
|
|
(allTimeActive && r.canned === 'alltime' && !canned.value)) {
|
|
btn.classList.add('active');
|
|
}
|
|
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) {
|
|
if (allTimeActive) {
|
|
instance.clear(false);
|
|
} else {
|
|
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);
|
|
});
|
|
})();
|