diff --git a/frontend/src/lib/components/Table/table.tsx b/frontend/src/lib/components/Table/table.tsx index 13b2351d8..040aadd68 100644 --- a/frontend/src/lib/components/Table/table.tsx +++ b/frontend/src/lib/components/Table/table.tsx @@ -17,6 +17,7 @@ export type TableHeading = { sizeInPercent: number; formatValue?: (value: string | number | null) => string; formatStyle?: (value: string | number | null) => React.CSSProperties; + subHeading?: TableHeading; }; }; @@ -118,6 +119,93 @@ function preprocessData(data: TableRow[]): IdentifiedTableRow { + const newHeadings: Omit = {}; + for (const col in headings) { + const subHeadings = headings[col].subHeading; + if (subHeadings) { + const flattenedSubHeadings = flattenHeadings(subHeadings); + for (const subCol in flattenedSubHeadings) { + newHeadings[`${subCol}`] = flattenedSubHeadings[subCol]; + } + } + newHeadings[col] = { + label: headings[col].label, + sizeInPercent: headings[col].sizeInPercent, + formatValue: headings[col].formatValue, + formatStyle: headings[col].formatStyle, + }; + } + return newHeadings; +} + export const Table: React.FC> = (props) => { const [layoutError, setLayoutError] = React.useState({ error: false, message: "" }); const [preprocessedData, setPreprocessedData] = React.useState[]>([]); @@ -126,6 +214,8 @@ export const Table: React.FC> = (props) => { const [sortColumnAndDirectionArray, setSortColumnAndDirectionArray] = React.useState< SortColumnAndDirectionElement[] >([]); + const [headerRows, setHeaderRows] = React.useState([]); + const [flattenedHeadings, setFlattenedHeadings] = React.useState>({}); const [prevData, setPrevData] = React.useState[]>([]); const [prevHeadings, setPrevHeadings] = React.useState({}); @@ -144,18 +234,28 @@ export const Table: React.FC> = (props) => { ); } - if (!isEqual(prevHeadings, props.headings) || !isEqual(prevHeadings, props.headings)) { + if (!isEqual(prevHeadings, props.headings) || !isEqual(prevData, props.data)) { setPrevHeadings(props.headings); - const maxNumberOfSubheadings = Object.keys(props.headings).length; + const info = extractInformationFromTableHeading(props.headings); + setHeaderRows(info.headerRows); for (const row of props.data) { - if (Object.keys(row).length !== maxNumberOfSubheadings) { + if (Object.keys(row).length !== info.numColumns) { setLayoutError({ error: true, message: "The number of headings does not match the number of data series.", }); break; } + if (Object.keys(row).some((col) => !info.dataColumnIds.includes(col))) { + setLayoutError({ + error: true, + message: "The data series column ids do not match the heading ids.", + }); + break; + } } + const newFlattenedHeadings = flattenHeadings(props.headings); + setFlattenedHeadings(newFlattenedHeadings); } function handlePointerOver(row: TableRow | null) { @@ -265,6 +365,60 @@ export const Table: React.FC> = (props) => { ); } + function makeHeadingRow(row: TableHeadingCellInformation[], depth: number): React.ReactNode { + const headingCells: React.ReactNode[] = []; + + for (const cell of row) { + headingCells.push( + +
+ {flattenedHeadings[cell.id].label} + {cell.colSpan === 1 ? makeSortButtons(cell.id) : null} +
+ {cell.colSpan === 1 && ( +
+ handleFilterChange(cell.id, e.target.value)} + endAdornment={ +
handleFilterChange(cell.id, "")} + > + +
+ } + wrapperStyle={{ + fontWeight: "normal", + fontSize: "0.5rem", + }} + /> +
+ )} + + ); + } + + return {headingCells}; + } + + function makeHeadings(): React.ReactNode { + const headingComponents: React.ReactNode[] = []; + for (let depth = 0; depth < headerRows.length; depth++) { + headingComponents.push(makeHeadingRow(headerRows[depth], depth)); + } + return <>{headingComponents}; + } + return (
> = (props) => { style={{ width: props.width, maxHeight: props.height }} > - - - {Object.keys(props.headings).map((col) => ( - - ))} - - + {makeHeadings()}) => const text = props.viewContext.useSettingsToViewInterfaceValue("text"); const derivedText = props.viewContext.useSettingsToViewInterfaceValue("derivedText"); + const heading: TableHeading = { + col1: { + label: "Column 1", + sizeInPercent: 30, + subHeading: { + "col1.1": { + label: "Column 1.1", + sizeInPercent: 50, + }, + "col1.2": { + label: "Column 1.2", + sizeInPercent: 50, + }, + }, + }, + col2: { + label: "Column 2", + sizeInPercent: 70, + }, + }; + + const data: TableRow[] = [ + { + "col1.1": "Row 1, Column 1.1", + "col1.2": "Row 1, Column 1.2", + col2: "Row 1, Column 2", + }, + { + "col1.1": "Row 2, Column 1.1", + "col1.2": "Row 2, Column 1.2", + col2: "Row 2, Column 2", + }, + ]; + return ( -
- - +
+
-
- {props.headings[col].label} - {makeSortButtons(col)} -
-
- handleFilterChange(col, e.target.value)} - endAdornment={ -
handleFilterChange(col, "")} - > - -
- } - wrapperStyle={{ - fontWeight: "normal", - fontSize: "0.5rem", - }} - /> -
-
); };