diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/data-table.vue b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/data-table.vue index f55122240fa452027d902f17af873d5516830478..c0d4399773d528c796468cd564ef03dccfea66b6 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/data-table.vue +++ b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/data-table.vue @@ -8,18 +8,20 @@ :data-set="currentPrimaryIVData.IVData" :headers="ivColumnHeadings" :key-order="ivValueNames" + :sort-by="['dateTime']" :title="`${currentPrimaryIVData.name} -- instantaneous value data`" /> </div> <div - v-show="currentSecondaryIVData.IVData.length" - id="iv-secondary-table-container" + v-show="currentSecondaryIVData.IVData.length" + id="iv-secondary-table-container" > <PaginatedTable - :data-set="currentSecondaryIVData.IVData" - :headers="ivColumnHeadings" - :key-order="ivValueNames" - :title="`${currentSecondaryIVData.name} -- instantaneous value data`" + :data-set="currentSecondaryIVData.IVData" + :headers="ivColumnHeadings" + :key-order="ivValueNames" + :sort-by="['dateTime']" + :title="`${currentSecondaryIVData.name} -- instantaneous value data`" /> </div> <div @@ -30,6 +32,7 @@ :data-set="currentGWData.GWData" :headers="gwColumnHeadings" :key-order="gwValueNames" + :sort-by="['dateTime']" :title="`${currentGWData.name} -- field visit data`" /> </div> diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.test.js index df64b1f159eeb90fd28c9a15d080e57ddc727f31..5c754f2123d3bed8093c6f2350c33225c00ec733 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.test.js @@ -64,7 +64,8 @@ describe('monitoring-location/components/hydrograph/components/data-table.vue', headers: ['Time', 'Result', 'Notes'], rowsPerPage: 2, title: 'Test Title', - keyOrder: ['time', 'value', 'notes'] + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] } }); const headers = wrapper.findAll('th'); @@ -97,16 +98,44 @@ describe('monitoring-location/components/hydrograph/components/data-table.vue', headers: ['Time', 'Result', 'Notes'], rowsPerPage: 2, title: 'Test Title', - keyOrder: ['time', 'value', 'notes'] + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] } }); const pageButtons = wrapper.findAll('ul li'); - expect(pageButtons).toHaveLength(4); + expect(pageButtons).toHaveLength(5); expect(pageButtons[0].text()).toBe('1'); expect(pageButtons[1].text()).toBe('2'); expect(pageButtons[2].text()).toBe('...'); expect(pageButtons[3].text()).toBe('5'); + expect(pageButtons[4].html()).toContain('navigate_next'); + }); + + it('Hides the pagination buttons if there is only one page', () => { + wrapper = mount(PaginatedTable, { + props: { + dataSet: [ + { + time: '12:00', + value: '3', + notes: 'sth 456' + }, + { + time: '12:15', + value: '453', + notes: 'sth 1' + }], + headers: ['Time', 'Result', 'Notes'], + rowsPerPage: 2, + title: 'Test Title', + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] + } + }); + + const pageButtons = wrapper.findAll('ul li'); + expect(pageButtons).toHaveLength(0); }); it('Changes the table when a page button is clicked', async() => { @@ -116,10 +145,11 @@ describe('monitoring-location/components/hydrograph/components/data-table.vue', headers: ['Time', 'Result', 'Notes'], rowsPerPage: 2, title: 'Test Title', - keyOrder: ['time', 'value', 'notes'] + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] } }); - const pageButtons = wrapper.findAll('ul li a'); + const pageButtons = wrapper.findAll('ul li button'); pageButtons[1].trigger('click'); await wrapper.vm.$nextTick(); const rows = wrapper.findAll('tbody tr'); @@ -142,33 +172,34 @@ describe('monitoring-location/components/hydrograph/components/data-table.vue', headers: ['Time', 'Result', 'Notes'], rowsPerPage: 2, title: 'Test Title', - keyOrder: ['time', 'value', 'notes'] + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] } }); let pageButtons = wrapper.findAll('ul li'); - expect(pageButtons).toHaveLength(4); + expect(pageButtons).toHaveLength(5); expect(pageButtons[0].text()).toBe('1'); expect(pageButtons[1].text()).toBe('2'); expect(pageButtons[2].text()).toBe('...'); expect(pageButtons[3].text()).toBe('5'); - wrapper.findAll('ul li a')[1].trigger('click'); + wrapper.findAll('ul li button')[1].trigger('click'); await wrapper.vm.$nextTick(); pageButtons = wrapper.findAll('ul li'); - expect(pageButtons).toHaveLength(5); - expect(pageButtons[0].text()).toBe('1'); - expect(pageButtons[1].text()).toBe('2'); - expect(pageButtons[2].text()).toBe('3'); - expect(pageButtons[3].text()).toBe('...'); - expect(pageButtons[4].text()).toBe('5'); + expect(pageButtons).toHaveLength(7); + expect(pageButtons[1].text()).toBe('1'); + expect(pageButtons[2].text()).toBe('2'); + expect(pageButtons[3].text()).toBe('3'); + expect(pageButtons[4].text()).toBe('...'); + expect(pageButtons[5].text()).toBe('5'); - wrapper.findAll('ul li a')[0].trigger('click'); + wrapper.findAll('ul li button')[0].trigger('click'); await wrapper.vm.$nextTick(); pageButtons = wrapper.findAll('ul li'); - expect(pageButtons).toHaveLength(4); + expect(pageButtons).toHaveLength(5); expect(pageButtons[0].text()).toBe('1'); expect(pageButtons[1].text()).toBe('2'); expect(pageButtons[2].text()).toBe('...'); @@ -182,7 +213,8 @@ describe('monitoring-location/components/hydrograph/components/data-table.vue', headers: ['Time', 'Result', 'Notes'], rowsPerPage: 2, title: 'Test Title', - keyOrder: ['time', 'value', 'notes'] + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] } }); const rows = wrapper.findAll('tbody tr'); @@ -198,5 +230,71 @@ describe('monitoring-location/components/hydrograph/components/data-table.vue', expect(rows[1].findAll('td')[0].text()).toBe('12:15'); expect(rows[1].findAll('td')[1].text()).toBe('453'); expect(rows[1].findAll('td')[2].text()).toBe('sth 1'); + + wrapper.find('svg').trigger('click'); + await wrapper.vm.$nextTick(); + + expect(rows).toHaveLength(2); + expect(rows[0].findAll('td')).toHaveLength(3); + expect(rows[0].findAll('td')[0].text()).toBe('14:15'); + expect(rows[0].findAll('td')[1].text()).toBe('30'); + expect(rows[0].findAll('td')[2].text()).toBe('sth 9'); + + expect(rows[1].findAll('td')).toHaveLength(3); + expect(rows[1].findAll('td')[0].text()).toBe('14:00'); + expect(rows[1].findAll('td')[1].text()).toBe('300'); + expect(rows[1].findAll('td')[2].text()).toBe('sth 8'); + }); + + it('Resets the page count when the dataSet changes', async() => { + wrapper = mount(PaginatedTable, { + props: { + dataSet: TEST_DATASET, + headers: ['Time', 'Result', 'Notes'], + rowsPerPage: 2, + title: 'Test Title', + keyOrder: ['time', 'value', 'notes'], + sortBy: ['time'] + } + }); + + wrapper.findAll('ul li button')[3].trigger('click'); + await wrapper.vm.$nextTick(); + + await wrapper.setProps({ + dataSet: [ + { + time: '12:00', + value: '3', + notes: 'sth 456' + }, + { + time: '12:15', + value: '453', + notes: 'sth 1' + }, + { + time: '12:30', + value: '123', + notes: 'sth 2' + }, + { + time: '12:45', + value: '35', + notes: 'sth 3' + }, + { + time: '13:00', + value: '33', + notes: 'sth 4' + }] + }); + + const pageButtons = wrapper.findAll('ul li'); + expect(pageButtons).toHaveLength(4); + expect(pageButtons[0].text()).toBe('1'); + expect(pageButtons[1].text()).toBe('2'); + expect(pageButtons[2].text()).toBe('3'); + expect(pageButtons[3].html()).toContain('navigate_next'); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.vue b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.vue index 6ef917c1317be91638e10f292bf806d0ebb87d47..cdde655b217c912fdedabedf94020e88fa0d8523 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.vue +++ b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/paginated-table.vue @@ -1,55 +1,75 @@ <template> - <table class="usa-table"> - <!-- eslint-disable vue/no-v-html --> - <caption v-html="myTitle" /> - <!--eslint-enable--> - <thead> - <tr> - <th - v-for="column in myHeaders" - :key="column" - scope="col" - > - {{ column }} - <a href="javascript:void(0);"> + <div> + <table class="usa-table data-table-pages"> + <!-- eslint-disable vue/no-v-html --> + <caption v-html="tableTitle" /> + <!--eslint-enable--> + <thead> + <tr> + <th + v-for="(column, columnNumber) in tableHeaders" + :key="column" + scope="col" + > + {{ column }} <svg - v-if="column === 'Time'" + v-if="trackedSortBy.includes(orderedListOfTableKeys[columnNumber])" class="usa-icon sort" aria-hidden="true" role="img" - @click="sortOrder === 'desc' ? sortOrder = 'asce' : sortOrder = 'desc'" + @click="sortTableColumn(columnNumber)" > - <use :xlink:href="sortIconURL" /> + <use :xlink:href="sortIcon(columnNumber)" /> </svg> - </a> - </th> - </tr> - </thead> - <tbody> - <tr - v-for="(dataObject, index) in pagedArray[page-1]" - :key="index" - > - <td - v-for="key in myKeyOrder" - :key="key" - :data-sort-value="key === 'dateTime' ? dataObject[key] : null" + </th> + </tr> + </thead> + <tbody> + <tr + v-for="(dataObject, index) in pagedArray[page-1]" + :key="index" > - {{ dataObject[key] }} - </td> - </tr> - </tbody> - <nav class="usa-pagination"> + <td + v-for="key in orderedListOfTableKeys" + :key="key" + > + {{ dataObject[key] }} + </td> + </tr> + </tbody> + </table> + <nav + v-if="data.length > 1" + class="usa-pagination" + > <ul class="usa-pagination__list"> + <li + v-if="page !== 1" + class="usa-pagination__item usa-pagination__arrow" + > + <button + class="usa-pagination__link usa-pagination__previous-page" + aria-label="Previous page" + @click="page = page - 1" + > + <svg + class="usa-icon" + aria-hidden="true" + role="img" + > + <use :xlink:href="previousArrowURL" /> + </svg> + </button> + </li> <li class="usa-pagination__item usa-pagination__page-no"> - <a - :href="`${page === 1 ? '' : 'javascript:void(0);'}` || null" + <button + :disabled="page === 1" :class="`usa-pagination__button ${page === 1 ? 'usa-current' : ''}`" aria-label="Page 1" @click="page=1" > {{ 1 }} - </a> + </button> </li> <li v-if="page > 3" @@ -61,38 +81,37 @@ v-if="3 <= page && page <= data.length" class="usa-pagination__item usa-pagination__page-no" > - <a + <button class="usa-pagination__button" :aria-label="`Page ${page - 1}`" - href="javascript:void(0);" @click="page=page - 1" > {{ page - 1 }} - </a> + </button> </li> <li v-if="1 < page && page < data.length" class="usa-pagination__item usa-pagination__page-no" > - <a + <button class="usa-pagination__button usa-current" :aria-label="`Page ${page}`" + :disabled="1 < page && page < data.length" > {{ page }} - </a> + </button> </li> <li v-if="1 <= page && page < (data.length - 1)" class="usa-pagination__item usa-pagination__page-no" > - <a + <button class="usa-pagination__button" :aria-label="`Page ${page + 1}`" - href="javascript:void(0);" @click="page=page + 1" > {{ page + 1 }} - </a> + </button> </li> <li v-if="page < data.length - 2" @@ -103,30 +122,49 @@ <li class="usa-pagination__item usa-pagination__page-no" > - <a - :href="`${page === data.length ? '' : 'javascript:void(0);'}` || null" + <button + :disabled="page === data.length" :class="`usa-pagination__button ${page === data.length ? 'usa-current' : ''}`" :aria-label="`Last page, page ${data.length}`" @click="page=data.length" > {{ data.length }} - </a> + </button> + </li> + <li + v-if="page !== data.length" + class="usa-pagination__item usa-pagination__arrow" + > + <button + class="usa-pagination__link usa-pagination__previous-page" + aria-label="Previous page" + @click="page = page + 1" + > + <svg + class="usa-icon" + aria-hidden="true" + role="img" + > + <use :xlink:href="nextArrowURL" /> + </svg> + </button> </li> </ul> </nav> - </table> + </div> </template> <script> import config from 'ui/config.js'; -import {ref, toRefs, computed} from 'vue'; +import {ref, toRefs, computed, watch} from 'vue'; /* * @vue-prop {Array of Objects} dataSet - array of objects to be displayed * @vue-prop {Array of Strings} headers - array of column headers * @vue-prop {Number} rowsPerPage * @vue-prop {String} title - * @vue-prop {Array of Strings} keyOrder - array of keys found in dataSet + * @vue-prop {Array of Strings} keyOrder - array of keys found in dataSet + * @vue-prop {Array of Strings} sortBy - subset of keyOrder that defines attributes that can be sorted on */ export default { name: 'PaginatedTable', @@ -150,17 +188,25 @@ export default { keyOrder: { type: Array, required: true + }, + sortBy: { + type: Array, + required: true } }, setup(props) { - const sortIconURL = `${config.STATIC_URL}img/sprite.svg#sort_arrow`; const trackedData = toRefs(props).dataSet; const trackedPagesPerRow = toRefs(props).rowsPerPage; const trackedKeyOrder = toRefs(props).keyOrder; + const trackedSortBy = toRefs(props).sortBy; const page = ref(1); - const sortOrder = ref('desc'); - const sortTarget = ref(trackedKeyOrder.value[0]); + const initialSortTarget = trackedKeyOrder.value[0]; + const sortOrder = ref(['desc', initialSortTarget]); + const upArrowURL = `${config.STATIC_URL}img/sprite.svg#arrow_drop_up`; + const downArrowURL = `${config.STATIC_URL}img/sprite.svg#arrow_drop_down`; + const previousArrowURL = `${config.STATIC_URL}img/sprite.svg#navigate_before`; + const nextArrowURL = `${config.STATIC_URL}img/sprite.svg#navigate_next`; Array.prototype.objectSort = function(sortParameter) { function compare(a, b) { @@ -168,8 +214,8 @@ export default { return !isNaN(num); } - let left = isNumeric(a[sortParameter]) ? +a[sortParameter] : a[sortParameter]; - let right = isNumeric(b[sortParameter]) ? +b[sortParameter] : b[sortParameter]; + let left = isNumeric(a[sortParameter]) ? Number(a[sortParameter]) : a[sortParameter]; + let right = isNumeric(b[sortParameter]) ? Number(b[sortParameter]) : b[sortParameter]; if (left < right) { return -1; @@ -185,13 +231,13 @@ export default { const pagedArray = computed(() => { let array = []; let dataCopy = [...trackedData.value]; - switch (sortOrder.value) { + switch (sortOrder.value[0]) { case 'desc': - dataCopy.objectSort(sortTarget.value); + dataCopy.objectSort(sortOrder.value[1]); dataCopy.reverse(); break; case 'asce': - dataCopy.objectSort(sortTarget.value); + dataCopy.objectSort(sortOrder.value[1]); break; } for (let i = 0; i < dataCopy.length; i += trackedPagesPerRow.value) { @@ -199,17 +245,32 @@ export default { } return array; }); + + watch(trackedData, () => { + page.value = 1; + }); + + const sortTableColumn = function(columnNumber) { + sortOrder.value = sortOrder.value[0] === 'desc' ? ['asce', trackedKeyOrder.value[columnNumber]] : ['desc', trackedKeyOrder.value[columnNumber]]; + }; + + const sortIcon = function(columnNumber) { + return sortOrder.value.join('') === ['desc', trackedKeyOrder.value[columnNumber]].join('') ? downArrowURL : upArrowURL; + }; return { - sortIconURL, pagedArray, data: pagedArray, page, - myHeaders: toRefs(props).headers, - myTitle: toRefs(props).title, - myKeyOrder: toRefs(props).keyOrder, + tableHeaders: toRefs(props).headers, + tableTitle: toRefs(props).title, + orderedListOfTableKeys: trackedKeyOrder, sortOrder, - mySortTarget: sortTarget.value + trackedSortBy, + previousArrowURL, + nextArrowURL, + sortTableColumn, + sortIcon }; } }; diff --git a/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js b/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js index b313a4f00422e84382d875e483241483891cea60..1d3684aa748460b6dce5311095d5a0d8134c1cac 100644 --- a/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js +++ b/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js @@ -21,7 +21,7 @@ export const getTimeRange = memoize((timeRangeKind) => state => state.hydrograph /* *Returns a selector function which returns the IV data for the dataKind - * @param {String} dataKind - 'primary' or 'compare' + * @param {String} dataKind - 'primary', 'secondary' or 'compare' * @return {Function} */ export const getIVData = memoize((dataKind) => state => state.hydrographData[`${dataKind}IVData`] || null); diff --git a/assets/src/styles/monitoring-location.scss b/assets/src/styles/monitoring-location.scss index c11a8fcd9e12499f79d947a8bfae55f30e943384..32ae13774fbca23b5e9fdd54bab2a62fc53192a2 100644 --- a/assets/src/styles/monitoring-location.scss +++ b/assets/src/styles/monitoring-location.scss @@ -94,9 +94,19 @@ body { margin-right: auto; @include uswds.u-maxw('desktop'); } + .beta-tag { @include uswds.u-bg('accent-warm-dark'); } + + .data-table-pages svg { + cursor: pointer; + } + + .data-table-pages+.usa-pagination { + justify-content: left; + } + } .rotate {