UNPKG

@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
'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