Skip to content

Commit

Permalink
add custom date range of report.
Browse files Browse the repository at this point in the history
1. Make dependencies version fixed
2. Add bootstrap-datepicker for ui of custom range
3. Add _size.scss for responsive
4. Small change in dropdown menu of date filter for responsive.

close hangxingliu/vscode-coding-tracker#15
reference #4
  • Loading branch information
hangxingliu committed Nov 15, 2017
1 parent 6c25903 commit c3380db
Show file tree
Hide file tree
Showing 17 changed files with 734 additions and 318 deletions.
2 changes: 2 additions & 0 deletions .vscode/cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"btns",
"CHANGELOG",
"coord",
"datepicker",
"Dropdown",
"echart",
"echarts",
Expand All @@ -18,6 +19,7 @@
"intelli",
"markline",
"mkdirs",
"MMDD",
"pcid",
"repo",
"resizer",
Expand Down
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/node_modules/**": true
}
},
"cSpell.words": [
"MMDD",
"datepicker"
]
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion frontend/lib/install-from-node-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const COMPONENTS = {
"dist/css/bootstrap.min.css",
"dist/js/bootstrap.min.js"
],
"bootstrap-datepicker": [
"dist/css/bootstrap-datepicker3.standalone.min.css",
"dist/js/bootstrap-datepicker.min.js"
],
"ionicons": [
"css/ionicons.min.css",
"fonts/ionicons.eot",
Expand Down Expand Up @@ -54,4 +58,4 @@ console.log('success!');
function failed(reason) {
console.error(`\n error: ${reason}\n`);
process.exit(1);
}
}
5 changes: 4 additions & 1 deletion frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<title><%= title.index %></title>

<link href="../lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="../lib/bootstrap-datepicker/dist/css/bootstrap-datepicker3.standalone.min.css" rel="stylesheet">
<link href="../lib/ionicons/css/ionicons.min.css" rel="stylesheet">
<link href="sass/index.css" rel="stylesheet">

Expand Down Expand Up @@ -43,17 +44,19 @@
<div class="modal-container">
<%- include("modules/dialog/status.pug") %>
<%- include("modules/dialog/share.pug") %>
<%- include("modules/dialog/custom_date_range.pug") %>
</div>

<!-- javascripts -->
<script src="../lib/jquery/dist/jquery.min.js"></script>
<script src="../lib/popper.js/dist/umd/popper.min.js"></script>
<script src="../lib/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="../lib/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script>
<script src="../lib/echarts/dist/echarts.min.js"></script>

<script src="js/index.js"></script>
<script async defer src="https://buttons.github.io/buttons.js"></script>

</body>

</html>
</html>
22 changes: 16 additions & 6 deletions frontend/src/js/reportFilter.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//@ts-check
/// <reference path="./index.d.ts" />

let { getYYYYMMDD } = require('./utils/utils');
let { getYYYYMMDD, getMMDD } = require('./utils/utils');
let customRangeDlg = require('./ui/customDateRangeDialog');

const CUSTOM = '20';
const RECENT_DAYS = [0, 7, 14, 30, 365];

/** @type {ReportFilter} */
Expand All @@ -19,9 +21,15 @@ function installReportRangeComponent() {
onClick.call($rangeDropdownItem[0]);

function onClick() {
//TODO: 20: custom date range
let rangeId = $(this).attr('data-range');
if (rangeId == CUSTOM)
return customRangeDlg.show((from, to) => {
$currentRange.text(getMMDD(from) + ' ~ ' + getMMDD(to));
setCustomRange(from, to);
});

$currentRange.text($(this).text());
setRange($(this).attr('data-range'));
setRange(rangeId);
}
}

Expand Down Expand Up @@ -61,8 +69,10 @@ function setRange(_rangeId) {
filter.from = from;
notifySubscribers();
}
function setCustomRange(todo, todo2, todo3, todo4, todo5) {

function setCustomRange(from ,to) {
filter.from = from;
filter.to = to;
notifySubscribers();
}

/** @param {string} repo */
Expand Down Expand Up @@ -94,4 +104,4 @@ module.exports = {
subscribe,
removeSubscribers,
getFilter
};
};
206 changes: 206 additions & 0 deletions frontend/src/js/ui/customDateRangeDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
//@ts-check

const HALF_DAY = 12 * 3600 * 1000;
const ONE_DAY = 2 * HALF_DAY;

let $dlg = $('#dlgCustomDateRange'),
$calendar = $('#rangeCalendar');

let $txtStart = $('#rangeStart'),
$txtEnd = $('#rangeEnd');

let $btnConfirm = $('#rangeConfirm');
let $txtStatus = $('#rangeDescription');

let hadSetupUI = false;
/** @type {(from: Date, to: Date) => any} */
let callback = null;

$dlg.on('show.bs.modal', () => {});
$dlg.on('shown.bs.modal', () => {});
$dlg.on('hidden.bs.modal', () => {});

module.exports = { show };

/**
* @param {(from: Date, to: Date) => any} _callback
*/
function show(_callback) {
if (!hadSetupUI) {
hadSetupUI = true;
setupUI();
}
callback = _callback;
$dlg.modal();
}

/**
* setting up bootstrap-date-picker as a range picker
*/
function setupUI() {
/** @type {Date} */
let startDate = null, endDate = null;

$calendar.datepicker({})
.on('changeMonth', updateUI.bind(this))
.on('changeDate', onChangeDate.bind(this));

$txtStart.keyup(updateFromInput).blur(updateFromInput);
$txtEnd.keyup(updateFromInput).blur(updateFromInput);

$btnConfirm.click(() => {
if (startDate == null || endDate == null)
return;
if (callback)
callback(startDate, endDate);
$dlg.modal('hide');
})

updateUI();

function updateFromInput() {
let strStart = $txtStart.val(), strEnd = $txtEnd.val();
let start = getDateFromString(String(strStart)),
end = getDateFromString(String(strEnd));
if (start == null || isFirstEarlierThenSecond(end, start))
return $btnConfirm.addClass('disabled');
startDate = start;
endDate = end;

// let date-picker navigate to start date
$calendar.datepicker('update', startDate);
updateUI(true);
}

/** @param {DatepickerEventObject} event */
function onChangeDate(event) {
let { date } = event;
console.log('onChangeDate:', date.getTime(), yyyymmdd(date));
if (startDate == null) {
startDate = date;
} else if (endDate == null) {
if (isFirstEarlierThenSecond(date, startDate)) {
endDate = startDate;
startDate = date;
} else {
endDate = date;
}
} else {
startDate = date; endDate = null;
}
updateUI();
}
function updateUI(dontUpdateInputBox = false) {
let dump = `datepicker - updateUI:\n` +
` start: ${startDate == null ? 'null' : startDate.getTime()} (${yyyymmdd(startDate)})\n` +
` end: ${endDate == null ? 'null' : endDate.getTime()} (${yyyymmdd(endDate)})`;
console.log(dump);

if (!dontUpdateInputBox) {
$txtStart.val(yyyymmdd(startDate));
$txtEnd.val(yyyymmdd(endDate));
}

let days = Math.floor(((+endDate + HALF_DAY) - (+startDate)) / ONE_DAY) + 1;
if (startDate != null && endDate != null) {
$txtStatus.html(`Total: <b>${days}</b> days`);
$btnConfirm.removeClass('disabled');
} else {
$txtStatus.text('');
$btnConfirm.addClass('disabled');
}


if (startDate == null && endDate == null)
return;

process.nextTick(() => {
$calendar.find('td[data-date]').each((i, e) => {
let $day = $(e),
dateObj = new Date(parseInt($day.data('date')));
let date = dateObj.getTime() + dateObj.getTimezoneOffset() * 60 * 1000;
removeClasses($day, 'start', 'end', 'active', 'selected');

let isStart = false, isEnd = false;
if (startDate && isDateEqual(startDate, date))
isStart = true, addClasses($day, 'start', 'active');
if (endDate && isDateEqual(endDate, date))
isEnd = true, addClasses($day, 'end', 'active');

if (isStart || isEnd)
return;

if (isDateInside(date, startDate, endDate))
addClasses($day, 'selected');
});
});
}
}

/**
* @param {JQuery} $jqDom
* @param {string[]} classes
*/
function addClasses($jqDom, ...classes) {
classes.forEach(cl => $jqDom.addClass(cl));
return classes;
}
/**
* @param {JQuery} $jqDom
* @param {string[]} classes
*/
function removeClasses($jqDom, ...classes) {
classes.forEach(cl => $jqDom.removeClass(cl));
return classes;
}

/** @param {Date} date */
function yyyymmdd(date) {
if (!date) return `--`;
return `${date.getFullYear()}-${to2(date.getMonth() + 1)}-${to2(date.getDate())}`;
}
function to2(num) { return num < 10 ? `0${num}` : `${num}`; }

function getDateFromString(str = '') {
str = str.trim();
if (!str || str == '--') return null;

let part = str.split(/\s*-\s*/);
let y = parseInt(part[0]), m = parseInt(part[1]), d = parseInt(part[2]);
if (isNaN(y) || isNaN(m) || isNaN(d)) return null;
if (y > 2038 || y < 1997 || m < 1 || m > 12 || d < 1 || d > 31) return null;

let date = new Date(y, m - 1, d, 0, 0, 0, 0);
// Invalid date:
if (date.getDate() != d || date.getMonth() != m - 1) return null;
return date;
}

/**
* @param {Date|number} date
* @param {Date|number} date2
*/
function isDateEqual(date, date2) {
return (+date) == (+date2);
}

/**
* @param {Date|number} first
* @param {Date|number} second
*/
function isFirstEarlierThenSecond(first, second) {
return (+first) < (+second);
}

/**
* Is date in [leftBorder, rightBorder]
* @param {Date|number} date
* @param {Date|number} leftBorder
* @param {Date|number} rightBorder
*/
function isDateInside(date, leftBorder, rightBorder) {
let v = +date;
if (v < +leftBorder) return false;
if (v > +rightBorder) return false;
return true;
}
23 changes: 10 additions & 13 deletions frontend/src/js/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ let Utils = {
getReadableTimeString,
getReadableTimeStringFromMap,

getYYYYMMDD,
getYYYYMMDD, getMMDD,
getChartDom,

basename
Expand Down Expand Up @@ -79,22 +79,19 @@ function getEmptyCodingWatchingObject() { return { coding: 0, watching: 0 }; }
*/
function getShortProjectName(projectName) { return (projectName.match(/.*(^|[\\/])(.+)$/) || [])[2] || projectName }

/**
* @param {string|number} num
* @returns {string}
*/

/** @param {string|number} num */
function to2(num) { return num == 0 ? '00' : num < 10 ? `0${num}` : `${num}` }
/**
* @param {Date} date
* @returns {string}
*/

/** @param {Date} date */
function getYYYYMMDD(date){ return `${date.getFullYear()}-${to2(date.getMonth() + 1)}-${to2(date.getDate())}`}
/**
* @param {Date} date
* @returns {string}
*/

/** @param {Date} date */
function getHHMM(date) { return `${getYYYYMMDD(date)} ${to2(date.getHours())}:00` }

/** @param {Date} date */
function getMMDD(date) { return `${to2(date.getMonth() + 1)}-${to2(date.getDate())}`}

/**
* @param {any[]} array
* @param {boolean} [desc]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mixin range(range, text)
a.dropdown-item(href='#', data-range=range)= text

mixin reportRangeDropdownMenu
.dropdown-menu.dropdown-menu-right
.dropdown-menu
h6.dropdown-header Report Date Range
+range("1", "Recent 7 Days")
+range("2", "Recent 14 Days")
Expand All @@ -14,4 +14,4 @@ mixin reportRangeDropdownMenu
+range("13", "This Month")
+range("14", "Last Month")
.dropdown-divider
+range("20", "Custom Date Range")
+range("20", "Custom Date Range")
Loading

0 comments on commit c3380db

Please sign in to comment.