diff --git a/src/webapp/components/table/performance-overview-table/MedianRow.tsx b/src/webapp/components/table/performance-overview-table/MedianRow.tsx new file mode 100644 index 00000000..cf6e2f5b --- /dev/null +++ b/src/webapp/components/table/performance-overview-table/MedianRow.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import _ from "../../../../domain/entities/generic/Collection"; +import { TableCell, TableRow } from "@material-ui/core"; +import styled from "styled-components"; +import { PerformanceOverviewTableProps } from "./PerformanceOverviewTable"; + +export type TableColumn = { + value: string; + label: string; + dark?: boolean; +}; + +type MedianRowProps = { + columns: TableColumn[]; + rows: { + [key: TableColumn["value"]]: string; + }[]; + calculateColumns: TableColumn["value"][]; +}; + +export const MedianRow: React.FC = React.memo( + ({ rows, columns, calculateColumns }) => { + const calculateMedian = ( + rows: PerformanceOverviewTableProps["rows"], + column: TableColumn["value"] + ) => { + const values = rows.map(row => Number(row[column])).filter(value => !isNaN(value)); + values.sort((a, b) => a - b); + const mid = Math.floor(values.length / 2); + return values.length % 2 !== 0 + ? values[mid] + : ((values[mid - 1] || 0) + (values[mid] || 0)) / 2; + }; + + return ( + + {columns.map((column, columnIndex) => ( + + {columnIndex === 0 && "Median"} + {calculateColumns.includes(column.value) + ? calculateMedian(rows, column.value) + : ""} + + ))} + + ); + } +); + +const FooterTableCell = styled(TableCell)<{ $boldUnderline: boolean }>` + background-color: ${props => props.theme.palette.common.greyLight}; + text-decoration: ${props => props.$boldUnderline && "underline"}; + font-weight: ${props => (props.$boldUnderline || !!props.color) && "600"}; +`; diff --git a/src/webapp/components/table/performance-overview-table/PercentTargetMetRow.tsx b/src/webapp/components/table/performance-overview-table/PercentTargetMetRow.tsx new file mode 100644 index 00000000..062631e6 --- /dev/null +++ b/src/webapp/components/table/performance-overview-table/PercentTargetMetRow.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import _ from "../../../../domain/entities/generic/Collection"; +import { TableCell, TableRow } from "@material-ui/core"; +import styled from "styled-components"; +import { PerformanceOverviewTableProps } from "./PerformanceOverviewTable"; + +export type TableColumn = { + value: string; + label: string; + dark?: boolean; +}; + +type PercentTargetMetRowProps = { + columns: TableColumn[]; + rows: { + [key: TableColumn["value"]]: string; + }[]; + columnRules: { + [key: TableColumn["value"]]: number; + }; + calculateColumns: TableColumn["value"][]; +}; + +export const PercentTargetMetRow: React.FC = React.memo( + ({ rows, columns, columnRules, calculateColumns }) => { + const calculatePercentTargetMet = ( + rows: PerformanceOverviewTableProps["rows"], + column: TableColumn["value"], + target: number + ) => { + const count = rows.filter(row => Number(row[column]) <= target).length; + const percentage = (count / rows.length) * 100 || 0; + return `${percentage.toFixed(0) || 0}%`; + }; + + return ( + + {columns.map((column, columnIndex) => { + const rule = columnRules[column.value] || 7; + + return ( + + {columnIndex === 0 && "% Target Met"} + + {calculateColumns.includes(column.value) + ? calculatePercentTargetMet(rows, column.value, rule) + : ""} + + ); + })} + + ); + } +); + +const FooterTableCell = styled(TableCell)<{ $boldUnderline: boolean }>` + background-color: ${props => props.theme.palette.common.greyLight}; + text-decoration: ${props => props.$boldUnderline && "underline"}; + font-weight: ${props => (props.$boldUnderline || !!props.color) && "600"}; +`; diff --git a/src/webapp/components/table/performance-overview-table/PerformanceOverviewTable.tsx b/src/webapp/components/table/performance-overview-table/PerformanceOverviewTable.tsx index d2483f46..574e5f60 100644 --- a/src/webapp/components/table/performance-overview-table/PerformanceOverviewTable.tsx +++ b/src/webapp/components/table/performance-overview-table/PerformanceOverviewTable.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; import _ from "../../../../domain/entities/generic/Collection"; import { Table, @@ -13,6 +13,8 @@ import { SearchInput } from "../../search-input/SearchInput"; import { Selector } from "../../selector/Selector"; import { Maybe } from "../../../../utils/ts-utils"; import i18n from "../../../../utils/i18n"; +import { MedianRow } from "./MedianRow"; +import { PercentTargetMetRow } from "./PercentTargetMetRow"; export type TableColumn = { value: string; @@ -20,7 +22,7 @@ export type TableColumn = { dark?: boolean; }; -type PerformanceOverviewTableProps = { +export type PerformanceOverviewTableProps = { columns: TableColumn[]; columnRules: { [key: TableColumn["value"]]: number; @@ -66,58 +68,6 @@ export const PerformanceOverviewTable: React.FC = return value <= rule ? "green" : "red"; }; - const calculateMedian = ( - rows: PerformanceOverviewTableProps["rows"], - column: TableColumn["value"] - ) => { - const values = rows.map(row => Number(row[column])).filter(value => !isNaN(value)); - values.sort((a, b) => a - b); - const mid = Math.floor(values.length / 2); - return values.length % 2 !== 0 - ? values[mid] - : ((values[mid - 1] || 0) + (values[mid] || 0)) / 2; - }; - - const calculatePercentTargetMet = ( - rows: PerformanceOverviewTableProps["rows"], - column: TableColumn["value"], - target: number - ) => { - const count = rows.filter(row => Number(row[column]) <= target).length; - const percentage = (count / rows.length) * 100 || 0; - return `${percentage.toFixed(0) || 0}%`; - }; - - const buildMedianRow = useMemo(() => { - return columns.map((column, columnIndex) => ( - - {columnIndex === 0 && "Median"} - {calculateColumns.includes(column.value) - ? calculateMedian(filteredRows, column.value) - : ""} - - )); - }, [filteredRows]); - - const buildPercentTargetMetRow = useMemo(() => { - return columns.map((column, columnIndex) => { - const rule = columnRules[column.value] || 7; - - return ( - - {columnIndex === 0 && "% Target Met"} - - {calculateColumns.includes(column.value) - ? calculatePercentTargetMet(filteredRows, column.value, rule) - : ""} - - ); - }); - }, [filteredRows]); - return ( @@ -162,8 +112,17 @@ export const PerformanceOverviewTable: React.FC = ))} ))} - {buildMedianRow} - {buildPercentTargetMetRow} + +