@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
277 lines (272 loc) • 10.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var urlHelper = require('../urlHelper.js');
var DateMapper = require('../DateMapper/DateMapper.js');
class DataTableBuilder {
fields;
sortData;
columns = [];
reportSummaries = {};
// Sortable headers only
reportQuery = null;
currentQueryParams = null;
dateMapper = new DateMapper.DateMapper();
constructor(fields, sortData = false) {
this.fields = fields;
this.sortData = sortData;
}
mapDate(isoDate) {
if (!isoDate)
return '';
return this.dateMapper.toDateString(isoDate, 'local-datetime-short-year');
}
mapBoolean(value) {
if (!value)
return '';
return value.substring(0, 1).toUpperCase() + value.substring(1).toLowerCase();
}
mapRow(rowData, extraClasses = '', overrideFields = []) {
return this.fields
.filter(f => this.columns.includes(f.name))
.map(f => {
const overrideField = overrideFields.find(o => o.name === f.name);
const field = overrideField ?? f;
return this.mapCell(field, rowData, extraClasses);
});
}
mapCell(field, rowData, extraClasses = '') {
const displayValue = rowData[field.name];
const textValue = this.mapCellValue(field, displayValue);
let fieldFormat = 'string';
let classes = extraClasses;
if (field.wordWrap) {
classes += ` data-table-cell-wrap-${field.wordWrap.toLowerCase()}`;
}
if (field.header) {
classes += ' govuk-table__header';
}
if (field.type === 'double' || field.type === 'long' || typeof displayValue === 'number') {
fieldFormat = 'numeric';
}
if (fieldFormat === 'string' && displayValue && typeof displayValue === 'string') {
const wordCount = displayValue.split(' ').length;
if (wordCount > 10) {
classes += ' data-table-cell-long-string';
}
}
const isHtml = field.type === 'HTML';
const cell = {
fieldName: field.name,
...(isHtml ? { html: textValue } : { text: textValue }),
format: fieldFormat,
classes: classes.trim(),
};
return cell;
}
mapCellValue(field, cellData) {
if (field.calculated) {
return cellData;
}
switch (field.type) {
case 'boolean':
return this.mapBoolean(cellData);
case 'date':
case 'time':
return this.mapDate(cellData);
default:
return cellData === undefined ? '' : cellData;
}
}
mapHeader() {
const WRAP_LENGTH = 20;
return this.fields
.filter(field => this.columns.includes(field.name))
.map(f => {
const { display, sortable, name } = f;
const displayLength = display.length;
const classes = displayLength > WRAP_LENGTH ? 'govuk-table__header--long' : '';
if (this.reportQuery) {
const { sortedAsc, sortColumn } = this.reportQuery;
if (sortable) {
const isCurrentSort = name === sortColumn;
const nextAsc = isCurrentSort ? !sortedAsc : true;
let sortDirection = 'none';
if (isCurrentSort) {
sortDirection = sortedAsc ? 'ascending' : 'descending';
}
const updateQueryParams = {
sortColumn: f.name,
sortedAsc: String(nextAsc),
selectedPage: '1',
};
const url = urlHelper.mergeAndStringifyQuery(this.currentQueryParams ?? {}, updateQueryParams, this.fields);
const visuallyHiddenText = `. ${sortDirection === 'none' ? 'Not sorted' : `Sorted ${sortDirection}`}. Click to sort ${nextAsc ? 'Ascending' : 'Descending'}`;
return {
html: `<a ` +
`data-column="${f.name}" ` +
`aria-describedby="${f.name}"` +
`class="data-table-header-button data-table-header-button-sort-${sortDirection} ${classes}" ` +
`href="${url}"` +
`>${f.display}<span id=${f.name} class="govuk-visually-hidden">${visuallyHiddenText}</span></a>`,
};
}
}
return {
text: f.display,
...(classes && { classes }),
};
});
}
mapData(data) {
const mappedHeaderSummary = this.mapSummary('table-header');
const mappedTableData = this.mergeCells(data.map(rowData => this.mapRow(rowData)));
const mappedFooterSummary = this.mapSummary('table-footer');
return mappedHeaderSummary.concat(mappedTableData).concat(mappedFooterSummary);
}
mergeCells(rows) {
const mergeFieldNames = this.fields
.filter(f => f.mergeRows)
.map(f => f.name);
if (mergeFieldNames.length === 0) {
return rows;
}
const occurrences = {};
mergeFieldNames.forEach(f => {
occurrences[f] = rows.reduce((accumulator, currentRow) => {
const currentCell = this.getCellByFieldName(currentRow, f);
let cellValue = '';
if (currentCell) {
cellValue = currentCell.text || currentCell.html || '';
}
return {
...accumulator,
[cellValue]: (accumulator[cellValue] ?? 0) + 1,
};
}, {});
});
return rows.map(row => {
let mergedRow = [...row];
mergeFieldNames.forEach(mergeFieldName => {
const currentRowCell = this.getCellByFieldName(row, mergeFieldName);
let cellValue;
let occurrencesOfValue;
if (currentRowCell && occurrences[mergeFieldName]) {
cellValue = currentRowCell.text || currentRowCell.html || '';
occurrencesOfValue = occurrences[mergeFieldName][cellValue];
switch (occurrencesOfValue) {
case -1:
mergedRow = mergedRow.filter(c => c.fieldName !== mergeFieldName);
break;
case 1:
break;
default:
currentRowCell.rowspan = occurrencesOfValue;
occurrences[mergeFieldName][cellValue] = -1;
}
}
});
return mergedRow;
});
}
getCellByFieldName(row, fieldName) {
return row.find(c => c.fieldName === fieldName);
}
mapSummary(template) {
if (this.reportSummaries[template]) {
return this.reportSummaries[template].flatMap(reportSummary => reportSummary.data.map(rowData => this.mapRow(rowData, `dpr-report-summary-cell dpr-report-summary-cell-${template}`, reportSummary.fields)));
}
return [];
}
sort(data) {
return this.appendSortKeyToData(data)
.sort(this.sortKeyComparison())
.map((d) => ({
...d,
}));
}
sortKeyComparison() {
return (a, b) => {
const aValue = a.sortKey;
const bValue = b.sortKey;
if (aValue === bValue) {
return 0;
}
if (aValue < bValue) {
return -1;
}
return 1;
};
}
appendSortKeyToData(data, fields = null) {
const sortFields = fields || this.fields;
return data.map(rowData => {
const sortKey = this.getSortKey(rowData, sortFields);
return {
...rowData,
sortKey,
};
});
}
mapNamesToFields(names) {
return names.map(s => this.fields.find(f => f.name === s)).filter(n => n !== undefined);
}
getSortKey(rowData, sortFields) {
return sortFields
.map(f => {
const value = rowData[f.name];
if (value && this.dateMapper.isDate(value)) {
return this.dateMapper.toDateString(value, 'iso');
}
return this.mapCellValue(f, value);
})
.join('-')
.toLowerCase();
}
convertDataTableToHtml(dataTable) {
const head = dataTable.head || [];
const headers = head.map(h => `<th scope='col' class='govuk-table__header'>${h.html ?? h.text}</th>`);
const rows = dataTable.rows.map(r => `<tr class='govuk-table__row'>${r
.map(c => `<td class='govuk-table__cell govuk-table__cell--${c.format} ${c.classes}'>${c.html ?? c.text}</td>`)
.join('')}</tr>`);
return ("<table class='govuk-table'>" +
`<thead class='govuk-table__head'>${headers.join('')}</thead>` +
`<tbody class='govuk-table__body'>${rows.join('')}</tbody>` +
'</table>');
}
withHeaderOptions({ reportQuery, columns, interactive, }) {
if (interactive && reportQuery) {
return this.withHeaderSortOptions(reportQuery);
}
return this.withNoHeaderOptions(columns);
}
withHeaderSortOptions(reportQuery) {
this.reportQuery = reportQuery;
this.columns = reportQuery.columns;
this.currentQueryParams = this.reportQuery.toRecordWithFilterPrefix();
return this;
}
withNoHeaderOptions(columns) {
this.columns = columns;
return this;
}
buildTable(data) {
const mappedData = this.mapData(this.sortData ? this.sort(data) : data);
return {
head: this.mapHeader(),
rows: mappedData,
rowCount: data.length,
colCount: this.columns.length,
};
}
withSummaries(reportSummaries) {
this.reportSummaries = reportSummaries;
return this;
}
withSortedData(sortData = true) {
this.sortData = sortData;
return this;
}
}
exports.DataTableBuilder = DataTableBuilder;
exports.default = DataTableBuilder;
//# sourceMappingURL=DataTableBuilder.js.map