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.

221 lines (187 loc) 7.97 kB
import Dict = NodeJS.Dict import { components } from '../../types/api' import { Cell, DataTable, FieldDefinition } from '../DataTableBuilder/types' import DataTableBuilder from '../DataTableBuilder/DataTableBuilder' import { distinct } from '../arrayUtils' import { ChildData, ParentChildSortKey } from './types' import logger from '../logger' import SectionedDataTableBuilder from '../SectionedDataTableBuilder/SectionedDataTableBuilder' export default class ParentChildDataTableBuilder extends SectionedDataTableBuilder { variant: components['schemas']['VariantDefinition'] childData: Array<ChildData> = [] constructor(variant: components['schemas']['VariantDefinition']) { const { specification } = variant const { sections, template } = specification super(specification) this.sections = sections this.template = template this.variant = variant } private createParentChildTable(parentData: Array<Dict<string>>, header: Cell[]) { let sectionedParentChildData: Dict<Dict<Array<Dict<string>>>> = {} // Get the parent-child joins definition data const joinFields = this.mapNamesToFields( this.variant.childVariants.flatMap((c) => c.joinFields).reduce(distinct, []), ) // Create the section keys and const parentChildKeys = this.calculateParentChildKeys(parentData, joinFields) parentChildKeys.forEach((parentKey) => { sectionedParentChildData[parentKey.sortKey] = { parent: [], } }) sectionedParentChildData = this.splitParentDataIntoSections(sectionedParentChildData, parentData, joinFields) sectionedParentChildData = this.splitChildDataIntoSections(parentChildKeys, sectionedParentChildData) const childDataTableBuilders = this.createChildDataTableBuilders() const parentChildTable = parentChildKeys.flatMap((key) => { const sectionData = sectionedParentChildData[key.sortKey] const parentSectionData = sectionData.parent return [header].concat(parentSectionData.map((r) => this.mapRow(r, 'dpr-parent-cell'))).concat( this.variant.childVariants .filter((childVariant) => sectionData[childVariant.id]) .map((childVariant) => { const dataTable = childDataTableBuilders[childVariant.id].buildTable(sectionData[childVariant.id]) return [ { classes: 'dpr-child-report-cell', format: 'string', html: `<div class='dpr-child-report'><h3 class="govuk-heading-s">${ childVariant.name }</h3><div class="dpr-child-report_table">${this.convertDataTableToHtml(dataTable)}</div></div>`, colspan: this.columns.length, }, ] }), ) }) return parentChildTable } private createParentChildSectionRows(parentData: Array<Dict<string>>, header: Cell[]) { const sectionsDetails = this.mapSections(parentData) const sectionedData = sectionsDetails.sectionedData as Dict<Array<Dict<string>>> const sectionedParentChildSectionedRows: { sectionDescription: string rows: Cell[][] count: number countDescription: string }[] = [] Object.keys(sectionedData).forEach((sectionDescription) => { const data = sectionedData[sectionDescription] as Array<Dict<string>> sectionedParentChildSectionedRows.push({ sectionDescription, ...this.getSectionCount(sectionedData, sectionDescription), rows: this.createParentChildTable(data, header), }) }) const rows = sectionedParentChildSectionedRows.flatMap((section, index) => { const sectionHeader = this.createSectionHeader( section.sectionDescription, index, section.count, section.countDescription, ) return [...sectionHeader, ...section.rows] }) return rows } private mapParentChildData(parentData: Array<Dict<string>>, header: Cell[]): Cell[][] { if (this.sections?.length) { return this.createParentChildSectionRows(parentData, header) } return this.createParentChildTable(parentData, header) } private createChildDataTableBuilders() { const childDataTables: Dict<DataTableBuilder> = this.variant.childVariants.reduce((previousValue, childVariant) => { const fieldNamesToDisplay = childVariant.specification.fields .filter((f) => f.visible || f.mandatory) .map((f) => f.name) const dataTableBuilder = new DataTableBuilder(childVariant.specification.fields) .withNoHeaderOptions(fieldNamesToDisplay) .withSortedData() return { ...previousValue, [childVariant.id]: dataTableBuilder, } }, {}) return childDataTables } private calculateParentChildKeys(parentData: Array<NodeJS.Dict<string>>, joinFields: FieldDefinition[]) { return parentData .map( (rowData): ParentChildSortKey => ({ sortKey: this.getSortKey(rowData, joinFields), childSortKeys: this.variant.childVariants.reduce((previousValue, childVariant) => { return { ...previousValue, [childVariant.id]: this.getSortKey(rowData, this.mapNamesToFields(childVariant.joinFields)), } }, {}), }), ) .reduce((previousValue: ParentChildSortKey[], sortKey: ParentChildSortKey) => { if (previousValue.find((v) => v.sortKey === sortKey.sortKey)) { return previousValue } return previousValue.concat(sortKey) }, []) } private splitParentDataIntoSections( sectionedParentChildData: NodeJS.Dict<NodeJS.Dict<Array<NodeJS.Dict<string>>>>, parentData: Array<NodeJS.Dict<string>>, joinFields: FieldDefinition[], ) { return parentData.reduce((previousValue, rowData) => { const parentKey: string = this.getSortKey(rowData, joinFields) const previousParentValue = previousValue[parentKey].parent const newParentValue = { parent: previousParentValue.concat(rowData), } return { ...previousValue, [parentKey]: newParentValue, } }, sectionedParentChildData) } private splitChildDataIntoSections( parentKeys: ParentChildSortKey[], sectionedParentChildData: Dict<Dict<Array<Dict<string>>>>, ): Dict<Dict<Array<Dict<string>>>> { const sectionedParentChildDataWithChildren = { ...sectionedParentChildData } this.variant.childVariants.forEach((childVariant) => { const childFields = this.mapNamesToFields(childVariant.joinFields) logger.info( 'DPR-Parent-child-template-debugging splitChildDataIntoSections', JSON.stringify({ childFields, parentKeys, }), ) const matchingChildData = this.childData.find((d) => d.id === childVariant.id) const data = matchingChildData ? matchingChildData.data : [] data .filter((rowData) => { const sortKey = this.getSortKey(rowData, childFields) return parentKeys.find((p) => p.childSortKeys[childVariant.id] === sortKey) }) .forEach((rowData) => { const sortKey = this.getSortKey(rowData, childFields) const parentSortKey = parentKeys.find((p) => p.childSortKeys[childVariant.id] === sortKey).sortKey const existingChildData = sectionedParentChildDataWithChildren[parentSortKey][childVariant.id] ?? [] sectionedParentChildDataWithChildren[parentSortKey][childVariant.id] = existingChildData.concat(rowData) }) }) return sectionedParentChildDataWithChildren } withChildData(childData: Array<ChildData>) { this.childData = childData return this } buildTable(data: Array<Dict<string>>): DataTable { return { head: null, rows: this.mapParentChildData(data, this.mapHeader(true, 'govuk-table__header')), rowCount: data.length, colCount: this.columns.length, } } }