UI: calendar widget fix (#15789)

* Months after current are disabled, regardless of endTimeFromResponse

* move tracked values to getters for consistency

* months for widget are calculated in getter and then rendered

* Styling for current month is mix of hover and readonly

* Fix tests

* Add changelog

* Reset display year to endTimeFromResponse on toggle calendar

* update resetDisplayYear and naming

* Add test for displayYear when opened
This commit is contained in:
Chelsea Shaw 2022-06-03 16:22:50 -05:00 committed by GitHub
parent 073cd369b6
commit 7f7ef1cfe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 94 deletions

3
changelog/15789.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fix inconsistent behavior in client count calendar widget
```

View File

@ -28,35 +28,76 @@ class CalendarWidget extends Component {
@tracked allMonthsNodeList = [];
@tracked displayYear = this.currentYear; // init to currentYear and then changes as a user clicks on the chevrons
@tracked disablePastYear = this.isObsoleteYear(); // if obsolete year, disable left chevron
@tracked disableFutureYear = this.isCurrentYear(); // if current year, disable right chevron
@tracked showCalendar = false;
@tracked tooltipTarget = null;
@tracked tooltipText = null;
get selectedMonthId() {
if (!this.args.endTimeFromResponse) return '';
const [year, monthIndex] = this.args.endTimeFromResponse;
return `${monthIndex}-${year}`;
}
get disableFutureYear() {
return this.displayYear === this.currentYear;
}
get disablePastYear() {
let startYear = parseInt(this.args.startTimeDisplay.split(' ')[1]);
return this.displayYear === startYear; // if on startYear then don't let them click back to the year prior
}
get widgetMonths() {
const displayYear = this.displayYear;
const currentYear = this.currentYear;
const currentMonthIdx = this.currentMonth;
const [startMonth, startYear] = this.args.startTimeDisplay.split(' ');
const startMonthIdx = this.args.arrayOfMonths.indexOf(startMonth);
return this.args.arrayOfMonths.map((month, idx) => {
const monthId = `${idx}-${displayYear}`;
let readonly = false;
// if widget is showing billing start year, disable if month is before start month
if (parseInt(startYear) === displayYear && idx < startMonthIdx) {
readonly = true;
}
// if widget showing current year, disable if month is current or later
if (displayYear === currentYear && idx >= currentMonthIdx) {
readonly = true;
}
return {
id: monthId,
month,
readonly,
current: monthId === `${currentMonthIdx}-${currentYear}`,
};
});
}
// HELPER FUNCTIONS (alphabetically) //
addClass(element, classString) {
element.classList.add(classString);
}
isCurrentYear() {
return this.currentYear === this.displayYear;
}
isObsoleteYear() {
// do not allow them to choose a year before the this.args.startTimeDisplay
let startYear = this.args.startTimeDisplay.split(' ')[1];
return this.displayYear.toString() === startYear; // if on startYear then don't let them click back to the year prior
}
removeClass(element, classString) {
element.classList.remove(classString);
}
resetDisplayYear() {
let setYear = this.currentYear;
if (this.args.endTimeDisplay) {
try {
const year = this.args.endTimeDisplay.split(' ')[1];
setYear = parseInt(year);
} catch (e) {
console.debug('Error resetting display year', e);
}
}
this.displayYear = setYear;
}
// ACTIONS (alphabetically) //
@action
addTooltip() {
if (this.isObsoleteYear()) {
if (this.disablePastYear) {
let previousYear = Number(this.displayYear) - 1;
this.tooltipText = `${previousYear} is unavailable because it is before your billing start month. Change your billing start month to a date in ${previousYear} to see data for this year.`; // set tooltip text
this.tooltipTarget = '#previous-year';
@ -66,53 +107,6 @@ class CalendarWidget extends Component {
@action
addYear() {
this.displayYear = this.displayYear + 1;
this.disableMonths();
this.disableFutureYear = this.isCurrentYear();
this.disablePastYear = this.isObsoleteYear();
}
@action
disableMonths() {
this.allMonthsNodeList = document.querySelectorAll('.is-month-list');
this.allMonthsNodeList.forEach((e) => {
// clear all is-readOnly classes and start over.
this.removeClass(e, 'is-readOnly');
let elementMonthId = parseInt(e.id.split('-')[0]); // dependent on the shape of the element id
// for current year
if (this.currentMonth <= elementMonthId) {
// only disable months when current year is selected
if (this.isCurrentYear()) {
e.classList.add('is-readOnly');
}
}
// compare for startYear view
if (this.displayYear.toString() === this.args.startTimeDisplay.split(' ')[1]) {
// if they are on the view where the start year equals the display year, check which months should not show.
let startMonth = this.args.startTimeDisplay.split(' ')[0]; // returns month name e.g. January
// return the index of the startMonth
let startMonthIndex = this.args.arrayOfMonths.indexOf(startMonth);
// then add readOnly class to any month less than the startMonth index.
if (startMonthIndex > elementMonthId) {
e.classList.add('is-readOnly');
}
}
// Compare values so the user cannot select an endTime after the endTime returned from counters/activity response on page load.
let yearEndTimeFromResponse = Number(this.args.endTimeFromResponse[0]);
let endMonth = this.args.endTimeFromResponse[1];
if (this.displayYear === yearEndTimeFromResponse) {
// add readOnly class to any month that is older (higher) than the endMonth index. (e.g. if nov is the endMonth of the endTimeDisplay, then 11 and 12 should not be displayed 10 < 11 and 10 < 12.)
if (endMonth < elementMonthId) {
e.classList.add('is-readOnly');
}
}
// if the year display higher than the endTime e.g. you're looking at 2022 and the returned endTime is 2021, all months should be disabled.
if (this.displayYear > yearEndTimeFromResponse) {
// all months should be disabled.
e.classList.add('is-readOnly');
}
});
}
@action removeTooltip() {
@ -126,23 +120,22 @@ class CalendarWidget extends Component {
D.actions.close(); // close the dropdown.
}
@action
selectEndMonth(month, year, D) {
selectEndMonth(monthId, D) {
const [monthIdx, year] = monthId.split('-');
this.toggleShowCalendar();
this.args.handleClientActivityQuery(month, year, 'endTime');
this.args.handleClientActivityQuery(parseInt(monthIdx), parseInt(year), 'endTime');
D.actions.close(); // close the dropdown.
}
@action
subYear() {
this.displayYear = this.displayYear - 1;
this.disableMonths();
this.disableFutureYear = this.isCurrentYear();
this.disablePastYear = this.isObsoleteYear();
}
@action
toggleShowCalendar() {
this.showCalendar = !this.showCalendar;
this.resetDisplayYear();
}
}
export default setComponentTemplate(layout, CalendarWidget);

View File

@ -82,6 +82,12 @@ $dark-gray: #535f73;
color: lighten($dark-gray, 30%);
pointer-events: none;
}
&.is-selected-month {
background-color: lighten($dark-gray, 30%);
color: white;
text-align: center;
pointer-events: none;
}
}
}

View File

@ -11,7 +11,9 @@
</D.Trigger>
<D.Content @defaultClass={{concat "popup-menu-content calendar-content" (if this.showCalendar " calendar-open")}}>
<nav class="box menu">
<div class="calendar-title is-subtitle-gray">DATE OPTIONS</div>
<div class="calendar-title is-subtitle-gray">
DATE OPTIONS
</div>
<ul class="menu-list">
<li class="action">
<button
@ -31,7 +33,9 @@
{{on "click" this.toggleShowCalendar}}
>
<div class="level is-mobile">
<span class="level-left">Custom end month</span>
<span class="level-left">
Custom end month
</span>
<Chevron class="has-text-grey-light level-right" />
</div>
</button>
@ -57,7 +61,9 @@
{{on "mouseleave" this.removeTooltip}}
/>
</button>
<p data-test-display-year>{{this.displayYear}}</p>
<p data-test-display-year>
{{this.displayYear}}
</p>
<button
data-test-future-year
type="button"
@ -78,22 +84,24 @@
offset="150px 0"
}}
<div class={{"calendar-tooltip"}}>
<p>{{this.tooltipText}}</p>
<p>
{{this.tooltipText}}
</p>
</div>
<div class="chart-tooltip-arrow"></div>
{{/modal-dialog}}
{{/if}}
</div>
<div {{did-insert this.disableMonths}} class="calendar-widget-grid calendar-widget">
{{#each @arrayOfMonths as |month index|}}
<div class="calendar-widget-grid calendar-widget">
{{#each this.widgetMonths as |m|}}
<button
data-test-calendar-month={{month}}
data-test-calendar-month={{m.month}}
type="button"
{{on "click" (fn this.selectEndMonth index this.displayYear D)}}
class="is-month-list"
id={{concat index "-" this.displayYear}}
class="is-month-list {{if m.readonly 'is-readOnly'}} {{if (eq m.id this.selectedMonthId) 'is-selected-month'}}"
id={{m.id}}
{{on "click" (fn this.selectEndMonth m.id D)}}
>
{{month}}
{{m.month}}
</button>
{{/each}}
</div>

View File

@ -5,26 +5,31 @@ import sinon from 'sinon';
import hbs from 'htmlbars-inline-precompile';
import calendarDropdown from 'vault/tests/pages/components/calendar-widget';
import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
import { subYears } from 'date-fns';
module('Integration | Component | calendar-widget', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
const CURRENT_YEAR = new Date().getFullYear();
const PREVIOUS_YEAR = subYears(new Date(), 1).getFullYear();
this.set('currentYear', CURRENT_YEAR);
this.set('previousYear', PREVIOUS_YEAR);
this.set('handleClientActivityQuery', sinon.spy());
this.set('handleCurrentBillingPeriod', sinon.spy());
this.set('arrayOfMonths', ARRAY_OF_MONTHS);
this.set('endTimeFromResponse', ['2022', 0]);
this.set('endTimeFromResponse', [CURRENT_YEAR, 0]);
});
test('it renders and can open the calendar view', async function (assert) {
await render(hbs`
<CalendarWidget
@arrayOfMonths={{arrayOfMonths}}
@endTimeDisplay={{"January 2022"}}
@endTimeDisplay={{concat "January " currentYear}}
@endTimeFromResponse={{endTimeFromResponse}}
@handleClientActivityQuery={{handleClientActivityQuery}}
@handleCurrentBillingPeriod={{handleCurrentBillingPeriod}}
@startTimeDisplay={{"February 2021"}}
@startTimeDisplay={{concat "February " previousYear}}
/>
`);
@ -36,11 +41,11 @@ module('Integration | Component | calendar-widget', function (hooks) {
await render(hbs`
<CalendarWidget
@arrayOfMonths={{arrayOfMonths}}
@endTimeDisplay={{"March 2022"}}
@endTimeDisplay={{concat "March " currentYear}}
@endTimeFromResponse={{endTimeFromResponse}}
@handleClientActivityQuery={{handleClientActivityQuery}}
@handleCurrentBillingPeriod={{handleCurrentBillingPeriod}}
@startTimeDisplay={{"February 2021"}}
@startTimeDisplay={{concat "February " previousYear}}
/>
`);
@ -48,39 +53,45 @@ module('Integration | Component | calendar-widget', function (hooks) {
assert.dom('[data-test-future-year]').isDisabled('Future year is disabled');
await calendarDropdown.clickPreviousYear();
assert.dom('[data-test-display-year]').hasText('2021', 'shows the previous year');
assert.dom('[data-test-display-year]').hasText(this.previousYear.toString(), 'shows the previous year');
assert
.dom('[data-test-calendar-month="January"]')
.hasClass('is-readOnly', 'January 2021 is disabled because it comes before February 2021');
.hasClass(
'is-readOnly',
`January ${this.previousYear} is disabled because it comes before startTimeDisplay`
);
});
test('it enables the current month but disables future months', async function (assert) {
test('it disables the current month', async function (assert) {
await render(hbs`
<CalendarWidget
@arrayOfMonths={{arrayOfMonths}}
@endTimeDisplay={{"January 2022"}}
@endTimeDisplay={{concat "January " currentYear}}
@endTimeFromResponse={{endTimeFromResponse}}
@handleClientActivityQuery={{handleClientActivityQuery}}
@handleCurrentBillingPeriod={{handleCurrentBillingPeriod}}
@startTimeDisplay={{"February 2021"}}
@startTimeDisplay={{concat "February " previousYear}}
/>
`);
await calendarDropdown.openCalendar();
const month = this.arrayOfMonths[new Date().getMonth()];
assert
.dom('[data-test-calendar-month="January"]')
.doesNotHaveClass('is-readOnly', 'January 2022 is enabled');
assert.dom('[data-test-calendar-month="February"]').hasClass('is-readOnly', 'February 2022 is enabled');
.dom(`[data-test-calendar-month="${month}"]`)
.hasClass('is-readOnly', `${month} ${this.currentYear} is disabled`);
// The component also disables all months after the current one, but this
// is tricky to test since it's based on browser time, so the behavior
// would be different in december than other months
});
test('it allows you to reset the billing period', async function (assert) {
await render(hbs`
<CalendarWidget
@arrayOfMonths={{arrayOfMonths}}
@endTimeDisplay={{"January 2022"}}
@endTimeDisplay={{concat "January " currentYear}}
@endTimeFromResponse={{endTimeFromResponse}}
@handleClientActivityQuery={{handleClientActivityQuery}}
@handleCurrentBillingPeriod={{handleCurrentBillingPeriod}}
@startTimeDisplay={{"February 2021"}}
@startTimeDisplay={{concat "February " previousYear}}
/>
`);
await calendarDropdown.menuToggle();
@ -92,11 +103,11 @@ module('Integration | Component | calendar-widget', function (hooks) {
await render(hbs`
<CalendarWidget
@arrayOfMonths={{arrayOfMonths}}
@endTimeDisplay={{"January 2022"}}
@endTimeDisplay={{concat "January " currentYear}}
@endTimeFromResponse={{endTimeFromResponse}}
@handleClientActivityQuery={{handleClientActivityQuery}}
@handleCurrentBillingPeriod={{handleCurrentBillingPeriod}}
@startTimeDisplay={{"February 2021"}}
@startTimeDisplay={{concat "February " previousYear}}
/>
`);
await calendarDropdown.openCalendar();
@ -104,8 +115,26 @@ module('Integration | Component | calendar-widget', function (hooks) {
await click('[data-test-calendar-month="October"]'); // select endTime of October 2021
assert.ok(this.handleClientActivityQuery.calledOnce, 'it calls the parents handleClientActivityQuery');
assert.ok(
this.handleClientActivityQuery.calledWith(9, 2021, 'endTime'),
this.handleClientActivityQuery.calledWith(9, this.previousYear, 'endTime'),
'Passes the month as an index, year and date type to the parent'
);
});
test('it displays the year from endTimeDisplay when opened', async function (assert) {
this.set('endTimeFromResponse', [this.previousYear, 11]);
await render(hbs`
<CalendarWidget
@arrayOfMonths={{arrayOfMonths}}
@endTimeDisplay={{concat "December " previousYear}}
@endTimeFromResponse={{endTimeFromResponse}}
@handleClientActivityQuery={{handleClientActivityQuery}}
@handleCurrentBillingPeriod={{handleCurrentBillingPeriod}}
@startTimeDisplay={{"March 2020"}}
/>
`);
await calendarDropdown.openCalendar();
assert
.dom('[data-test-display-year]')
.hasText(this.previousYear.toString(), 'Shows year from the end response');
});
});