From 02ca66b0e7b8413b428acd557630a3d85980def3 Mon Sep 17 00:00:00 2001 From: lublagg Date: Mon, 30 Sep 2024 14:41:45 -0400 Subject: [PATCH] Refactor table cells + precisions / attrTypes / visibilities. --- src/components/app.tsx | 4 +- .../common}/add-attribute-button.scss | 0 .../common}/add-attribute-button.tsx | 4 +- .../common}/draggable-table-tags.tsx | 24 ++- .../common}/editable-table-cell.scss | 0 .../common}/editable-table-cell.tsx | 10 +- .../common}/editable-table-header.tsx | 0 .../nested-table-view/common/table-cells.tsx | 50 +++++ .../common}/table-headers.tsx | 4 +- .../common}/tables.scss | 3 +- .../flat}/flat-table.tsx | 49 ++--- .../landscape/landscape-table.tsx} | 61 +++--- .../{ => nested-table-view}/nested-table.scss | 0 .../{ => nested-table-view}/nested-table.tsx | 83 ++------ .../portrait/portrait-table-row.tsx | 136 +++++++++++++ .../portrait/portrait-table.tsx | 75 +++++++ src/components/portrait-view.tsx | 189 ------------------ src/components/table-cell.tsx | 67 ------- src/models/attributes.ts | 1 + src/models/collections.ts | 27 +++ src/types.ts | 9 +- src/utils/utils.ts | 60 +++--- 22 files changed, 417 insertions(+), 439 deletions(-) rename src/components/{ => nested-table-view/common}/add-attribute-button.scss (100%) rename src/components/{ => nested-table-view/common}/add-attribute-button.tsx (87%) rename src/components/{ => nested-table-view/common}/draggable-table-tags.tsx (92%) rename src/components/{ => nested-table-view/common}/editable-table-cell.scss (100%) rename src/components/{ => nested-table-view/common}/editable-table-cell.tsx (79%) rename src/components/{ => nested-table-view/common}/editable-table-header.tsx (100%) create mode 100644 src/components/nested-table-view/common/table-cells.tsx rename src/components/{ => nested-table-view/common}/table-headers.tsx (94%) rename src/components/{ => nested-table-view/common}/tables.scss (99%) rename src/components/{ => nested-table-view/flat}/flat-table.tsx (66%) rename src/components/{landscape-view.tsx => nested-table-view/landscape/landscape-table.tsx} (74%) rename src/components/{ => nested-table-view}/nested-table.scss (100%) rename src/components/{ => nested-table-view}/nested-table.tsx (63%) create mode 100644 src/components/nested-table-view/portrait/portrait-table-row.tsx create mode 100644 src/components/nested-table-view/portrait/portrait-table.tsx delete mode 100644 src/components/portrait-view.tsx delete mode 100644 src/components/table-cell.tsx diff --git a/src/components/app.tsx b/src/components/app.tsx index a9f3b0a..7ca9c1e 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import { InteractiveState, useCodapState } from "../hooks/useCodapState"; -import { NestedTable } from "./nested-table"; +import { NestedTable } from "./nested-table-view/nested-table"; import { Hierarchy } from "./hierarchy-view/hierarchy"; import { CardView } from "./card-view/card-view"; import { ICaseObjCommon } from "../types"; @@ -100,7 +100,7 @@ function App() { ; + attrTypes: Record; editCaseValue: (newValue: string, caseObj: IProcessedCaseObj, attrTitle: string) => Promise; } export const DraggagleTableData: React.FC> = observer(function DraggagleTableData(props) { - const {collectionId, attrTitle, children, caseObj, isParent, parentLevel=0, editCaseValue} = props; + const {collectionId, attrTitle, attrTypes, caseObj, isParent, parentLevel=0, precisions, editCaseValue} = props; const {dragOverId, dragSide} = useDraggableTableContext(); const {style} = getIdAndStyle(collectionId, attrTitle, dragOverId, dragSide); const {tableScrollTop, scrollY} = useTableTopScrollTopContext(); + const cellValue = caseObj.values.get(attrTitle); const cellRef = useRef(null); @@ -242,6 +246,8 @@ export const DraggagleTableData: React.FC ); }; @@ -255,7 +261,9 @@ export const DraggagleTableData: React.FC {isParent ? <> - {children} + + {getDisplayValue(cellValue, attrTitle, attrTypes, precisions)} +
diff --git a/src/components/editable-table-cell.scss b/src/components/nested-table-view/common/editable-table-cell.scss similarity index 100% rename from src/components/editable-table-cell.scss rename to src/components/nested-table-view/common/editable-table-cell.scss diff --git a/src/components/editable-table-cell.tsx b/src/components/nested-table-view/common/editable-table-cell.tsx similarity index 79% rename from src/components/editable-table-cell.tsx rename to src/components/nested-table-view/common/editable-table-cell.tsx index 30e5efe..7c35f55 100644 --- a/src/components/editable-table-cell.tsx +++ b/src/components/nested-table-view/common/editable-table-cell.tsx @@ -2,19 +2,23 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import { Editable, EditablePreview, EditableInput } from "@chakra-ui/react"; import { IResult } from "@concord-consortium/codap-plugin-api"; -import { IProcessedCaseObj } from "../types"; +import { IProcessedCaseObj } from "../../../types"; import css from "./editable-table-cell.scss"; +import { getDisplayValue } from "../../../utils/utils"; interface IProps { attrTitle: string; case: IProcessedCaseObj; editCaseValue: (newValue: string, cCase: IProcessedCaseObj, attrTitle: string) => Promise; + precisions: Record; + attrTypes: Record; } export const EditableTableCell = observer(function EditableTableCell(props: IProps) { - const { attrTitle, case: cCase, editCaseValue } = props; - const displayValue = cCase.values.get(attrTitle); + const { attrTitle, case: cCase, editCaseValue, attrTypes, precisions } = props; + const cellValue = cCase.values.get(attrTitle); + const displayValue = getDisplayValue(cellValue, attrTitle, attrTypes, precisions); const [editingValue, setEditingValue] = useState(displayValue); const [isEditing, setIsEditing] = useState(false); diff --git a/src/components/editable-table-header.tsx b/src/components/nested-table-view/common/editable-table-header.tsx similarity index 100% rename from src/components/editable-table-header.tsx rename to src/components/nested-table-view/common/editable-table-header.tsx diff --git a/src/components/nested-table-view/common/table-cells.tsx b/src/components/nested-table-view/common/table-cells.tsx new file mode 100644 index 0000000..e8af045 --- /dev/null +++ b/src/components/nested-table-view/common/table-cells.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { IProcessedCaseObj } from "../../../types"; +import { DraggagleTableData } from "./draggable-table-tags"; +import { IResult } from "@concord-consortium/codap-plugin-api"; + +interface IProps { + collectionId: number; + rowKey: string; + cCase: IProcessedCaseObj; + precisions: Record; + attrTypes: Record; + attrVisibilities: Record; + isParent?: boolean; + parentLevel?: number; + selectedDataSetName: string; + editCaseValue: (newValue: string, caseObj: IProcessedCaseObj, attrTitle: string) => Promise; +} + +export const TableCells = (props: IProps) => { + const { collectionId, rowKey, cCase, precisions, attrTypes, attrVisibilities, isParent, parentLevel, + selectedDataSetName, editCaseValue } = props; + if (!selectedDataSetName) return null; + const aCase = cCase.values; + return ( + <> + {[...aCase.keys()].map((key, index) => { + const cellValue = aCase.get(key); + const isHidden = attrVisibilities[key]; + if (key === "id" || (typeof cellValue !== "string" && typeof cellValue !== "number") || isHidden ) { + return null; + } + + return ( + + ); + })} + + ); +}; diff --git a/src/components/table-headers.tsx b/src/components/nested-table-view/common/table-headers.tsx similarity index 94% rename from src/components/table-headers.tsx rename to src/components/nested-table-view/common/table-headers.tsx index 093953c..d3f3c63 100644 --- a/src/components/table-headers.tsx +++ b/src/components/nested-table-view/common/table-headers.tsx @@ -1,7 +1,7 @@ import React from "react"; import { DraggableTableHeader } from "./draggable-table-tags"; -import { IDataSet, Values } from "../types"; -import { isNewAttribute } from "../utils/utils"; +import { IDataSet, Values } from "../../../types"; +import { isNewAttribute } from "../../../utils/utils"; interface MapHeadersFromValuesProps { collectionId: number; diff --git a/src/components/tables.scss b/src/components/nested-table-view/common/tables.scss similarity index 99% rename from src/components/tables.scss rename to src/components/nested-table-view/common/tables.scss index 77cd562..c2c9b4c 100644 --- a/src/components/tables.scss +++ b/src/components/nested-table-view/common/tables.scss @@ -265,10 +265,9 @@ table.draggableTableContainer { position: relative; .cellTextValue{ position: absolute; - width: calc(100% - 8px); display: flex; flex-wrap: wrap; - top: 0; + width: calc(100% - 8px); } } diff --git a/src/components/flat-table.tsx b/src/components/nested-table-view/flat/flat-table.tsx similarity index 66% rename from src/components/flat-table.tsx rename to src/components/nested-table-view/flat/flat-table.tsx index 9df4bb8..6fbca05 100644 --- a/src/components/flat-table.tsx +++ b/src/components/nested-table-view/flat/flat-table.tsx @@ -1,13 +1,13 @@ import React from "react"; import { observer } from "mobx-react-lite"; import { IResult } from "@concord-consortium/codap-plugin-api"; -import { IProcessedCaseObj, ITableProps } from "../types"; -import { DraggableTableContainer, DraggableTableHeader } from "./draggable-table-tags"; -import { isNewAttribute, getAttrPrecisions, getAttrTypes, getAttrVisibility } from "../utils/utils"; -import { TableCell } from "./table-cell"; -import { AddAttributeButton } from "./add-attribute-button"; +import { IProcessedCaseObj, ITableProps } from "../../../types"; +import { DraggableTableContainer, DraggableTableHeader } from "../common/draggable-table-tags"; +import { isNewAttribute } from "../../../utils/utils"; +import { TableCells } from "../common/table-cells"; +import { AddAttributeButton } from "../common/add-attribute-button"; -import css from "./tables.scss"; +import css from "../common/tables.scss"; interface IFlatProps extends ITableProps { cases: IProcessedCaseObj[] @@ -16,15 +16,13 @@ interface IFlatProps extends ITableProps { } export const FlatTable = observer(function FlatTable(props: IFlatProps) { - const {selectedDataSet, collections, collectionClasses, handleSortAttribute, showHeaders, + const {selectedDataSet, collectionsModel, collectionClasses, handleSortAttribute, showHeaders, editCaseValue, renameAttribute, handleAddAttribute } = props; - const collection = collections[0]; + const { collections, attrVisibilities, attrPrecisions, attrTypes } = collectionsModel; + const collection = collectionsModel.collections[0]; const {className} = collectionClasses[0]; - const attrVisibilities = getAttrVisibility(collections); const collectionAttrsToUse = collection.attrs.filter(attr => !attrVisibilities[attr.title]); const titles = collectionAttrsToUse.map(attr => attr.title); - const precisions = getAttrPrecisions(collections); - const attrTypes = getAttrTypes(collections); return ( @@ -70,25 +68,16 @@ export const FlatTable = observer(function FlatTable(props: IFlatProps) { }); return ( - {caseValuesKeys.map((key, i) => { - return ( - - ); - })} + ); })} diff --git a/src/components/landscape-view.tsx b/src/components/nested-table-view/landscape/landscape-table.tsx similarity index 74% rename from src/components/landscape-view.tsx rename to src/components/nested-table-view/landscape/landscape-table.tsx index 2f3ceff..2da9d7f 100644 --- a/src/components/landscape-view.tsx +++ b/src/components/nested-table-view/landscape/landscape-table.tsx @@ -1,15 +1,16 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; -import { DraggableTableHeader } from "./draggable-table-tags"; -import { getAttrPrecisions, getAttrTypes, getAttrVisibility } from "../utils/utils"; -import { TableHeaders } from "./table-headers"; +import { ICollection, IProcessedCaseObj, ITableProps } from "../../../types"; +import { DraggableTableHeader } from "../common/draggable-table-tags"; +import { TableHeaders } from "../common/table-headers"; +import { TableCells } from "../common/table-cells"; -import css from "./tables.scss"; +import css from "../common/tables.scss"; -export const LandscapeView = observer(function LandscapeView(props: ITableProps) { - const {mapCellsFromValues, showHeaders, collectionClasses, getClassName, selectedDataSet, - collections, getValueLength, paddingStyle, handleSortAttribute, renameAttribute} = props; +export const LandscapeTable = observer(function LandscapeView(props: ITableProps) { + const { showHeaders, collectionClasses, getClassName, selectedDataSet, collectionsModel, getValueLength, + paddingStyle, handleSortAttribute, renameAttribute, editCaseValue } = props; + const { collections, attrPrecisions, attrTypes, attrVisibilities } = collectionsModel; const renderNestedTable = (parentColl: ICollection) => { const headers = parentColl.cases.map((caseObj) => caseObj.values); @@ -20,9 +21,6 @@ export const LandscapeView = observer(function LandscapeView(props: ITableProps) const parentCase = parentColl.cases[0]; const valueCount = getValueLength(firstRowValues); const className = getClassName(parentColl.cases[0]); - const precisions = getAttrPrecisions(collections); - const attrTypes = getAttrTypes(collections); - const attrVisibilities = getAttrVisibility(collections); return ( <> {showHeaders && @@ -38,7 +36,7 @@ export const LandscapeView = observer(function LandscapeView(props: ITableProps) rowKey={`first-row`} values={values} attrVisibilities={attrVisibilities} - selectedDataSet={props.selectedDataSet} + selectedDataSet={selectedDataSet} handleSortAttribute={handleSortAttribute} renameAttribute={renameAttribute} /> @@ -46,11 +44,21 @@ export const LandscapeView = observer(function LandscapeView(props: ITableProps) })} - {firstRowValues.map(values => - mapCellsFromValues( - parentColl.id, "first-row", parentCase, precisions, attrTypes, attrVisibilities - )) - } + {firstRowValues.map((values, idx) => { + return ( + + ); + })} {parentColl.cases.map((caseObj) => { @@ -74,9 +82,6 @@ export const LandscapeView = observer(function LandscapeView(props: ITableProps) const renderColFromCaseObj = (collection: ICollection, caseObj: IProcessedCaseObj, index?: number) => { const {children, values} = caseObj; const isFirstIndex = index === 0; - const precisions = getAttrPrecisions(collections); - const attrTypes = getAttrTypes(collections); - const attrVisibilities = getAttrVisibility(collections); if (!children.length) { const className = getClassName(caseObj); @@ -94,17 +99,23 @@ export const LandscapeView = observer(function LandscapeView(props: ITableProps) rowKey={`first-row-${index}`} values={values} attrVisibilities={attrVisibilities} - selectedDataSet={props.selectedDataSet} + selectedDataSet={selectedDataSet} handleSortAttribute={handleSortAttribute} renameAttribute={renameAttribute} /> } - {mapCellsFromValues( - collection.id, `row-${index}`, caseObj, precisions, attrTypes, attrVisibilities - ) - } + ); diff --git a/src/components/nested-table.scss b/src/components/nested-table-view/nested-table.scss similarity index 100% rename from src/components/nested-table.scss rename to src/components/nested-table-view/nested-table.scss diff --git a/src/components/nested-table.tsx b/src/components/nested-table-view/nested-table.tsx similarity index 63% rename from src/components/nested-table.tsx rename to src/components/nested-table-view/nested-table.tsx index 89ba69f..61486f1 100644 --- a/src/components/nested-table.tsx +++ b/src/components/nested-table-view/nested-table.tsx @@ -1,14 +1,14 @@ import React, { useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react-lite"; import { IResult } from "@concord-consortium/codap-plugin-api"; -import { InteractiveState } from "../hooks/useCodapState"; -import { ICollection, IProcessedCaseObj, Values, ICollectionClass, IDataSet, ICollections } from "../types"; -import { PortraitView } from "./portrait-view"; -import { Menu } from "./menu"; -import { LandscapeView } from "./landscape-view"; -import { FlatTable } from "./flat-table"; -import { DraggableTableContext, useDraggableTable } from "../hooks/useDraggableTable"; -import { DraggagleTableData } from "./draggable-table-tags"; +import { InteractiveState } from "../../hooks/useCodapState"; +import { ICollection, IProcessedCaseObj, Values, ICollectionClass, IDataSet } from "../../types"; +import { DraggableTableContext, useDraggableTable } from "../../hooks/useDraggableTable"; +import { CollectionsModelType } from "../../models/collections"; +import { Menu } from "../menu"; +import { PortraitTable } from "./portrait/portrait-table"; +import { LandscapeTable } from "./landscape/landscape-table"; +import { FlatTable } from "./flat/flat-table"; import css from "./nested-table.scss"; @@ -19,7 +19,7 @@ const none = ""; interface IProps { selectedDataSet: IDataSet | null; dataSets: IDataSet[]; - collections: ICollections; + collectionsModel: CollectionsModelType; cases: IProcessedCaseObj[]; interactiveState: InteractiveState; handleSelectDataSet: (e: React.ChangeEvent, defaultDisplayMode?: string) => void; @@ -34,12 +34,13 @@ interface IProps { } export const NestedTable = observer(function NestedTable(props: IProps) { - const {selectedDataSet, dataSets, collections, cases, interactiveState, + const {selectedDataSet, dataSets, collectionsModel, cases, interactiveState, handleSelectDataSet, updateInteractiveState, handleShowComponent, handleUpdateAttributePosition, handleCreateCollectionFromAttribute, handleSortAttribute, editCaseValue, renameAttribute, handleAddAttribute} = props; const [collectionClasses, setCollectionClasses] = useState([]); const [paddingStyle, setPaddingStyle] = useState>({padding: "0px"}); + const collections = collectionsModel.collections; const draggableTable = useDraggableTable({ collections, @@ -93,60 +94,6 @@ export const NestedTable = observer(function NestedTable(props: IProps) { updateInteractiveState({displayMode: mode}); }, [updateInteractiveState]); - const mapCellsFromValues = (collectionId: number, rowKey: string, cCase: IProcessedCaseObj, - precisions: Record, attrTypes: Record, - attrVisibilities: Record, isParent?: boolean, parentLevel?: number) => { - if (!selectedDataSet) return null; - - const aCase = cCase.values; - return [...aCase.keys()].map((key, index) => { - if (key === "id") return null; - - const isWholeNumber = aCase.get(key) % 1 === 0; - const precision = precisions[key]; - // Numbers are sometimes passed in from CODAP as a string so we use the attribute type to - // determine if it should be parsed as a number. - // Numbers that are whole numbers are treated as integers, so we should ignore the precision. - // Numeric cells that are empty should be treated as empty strings. - const isNumericType = attrTypes[key] === "numeric"; - const hasValue = aCase.get(key) !== ""; - const parsedValue = parseFloat(aCase.get(key)); - const isNumber = !isNaN(parsedValue); - const hasPrecision = precision !== undefined; - const defaultValue = aCase.get(key); - const isNumberType = typeof aCase.get(key) === "number"; - let val; - if (isNumericType && hasValue && isNumber) { - val = isWholeNumber ? parseInt(aCase.get(key), 10) - : parsedValue.toFixed(hasPrecision ? precision : 2); - } else if (!isNumericType && isNumberType && hasValue) { - val = defaultValue.toFixed(hasPrecision ? precision : 2); - } else { - val = defaultValue; - } - - if (attrVisibilities[key]) { - return null; - } - if (typeof val === "string" || typeof val === "number") { - return ( - - {val} - - ); - } - }); - }; - const getValueLength = (firstRow: Array) => { let valueCount = 0; firstRow.forEach((values: Values) => { @@ -161,8 +108,8 @@ export const NestedTable = observer(function NestedTable(props: IProps) { const isNoHierarchy = collections.length === 1; const classesExist = collectionClasses.length > 0; - const tableProps = {showHeaders: interactiveState.showHeaders, collectionClasses, collections, selectedDataSet, - getClassName, mapCellsFromValues, getValueLength, paddingStyle, editCaseValue, handleSortAttribute, + const tableProps = {showHeaders: interactiveState.showHeaders, collectionClasses, collectionsModel, + selectedDataSet, getClassName, getValueLength, paddingStyle, editCaseValue, handleSortAttribute, dataSetName: selectedDataSet.name, renameAttribute, handleAddAttribute}; const flatProps = {...tableProps, cases}; if (isNoHierarchy && classesExist) { @@ -170,9 +117,9 @@ export const NestedTable = observer(function NestedTable(props: IProps) { } else { return ( interactiveState.displayMode === portrait ? - : + : interactiveState.displayMode === landscape ? - : + :
); } diff --git a/src/components/nested-table-view/portrait/portrait-table-row.tsx b/src/components/nested-table-view/portrait/portrait-table-row.tsx new file mode 100644 index 0000000..b7fb04e --- /dev/null +++ b/src/components/nested-table-view/portrait/portrait-table-row.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import { IProcessedCaseObj, ITableProps } from "../../../types"; +import { observer } from "mobx-react-lite"; +import { useTableHeaderFocusContext } from "../../../hooks/useTableHeaderFocusContext"; +import { TableCells } from "../common/table-cells"; +import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "../common/draggable-table-tags"; + +import css from "../common/tables.scss"; +import { TableHeaders } from "../common/table-headers"; + +export type PortraitViewRowProps = { + caseObj: IProcessedCaseObj, index?: null | number, + isParent: boolean, parentLevel?: number + dataSetName: string, hasFocusBeenSet?: boolean +} & ITableProps; + +export const PortraitTableRow = observer(function PortraitViewRow(props: PortraitViewRowProps) { + const { paddingStyle, showHeaders, getClassName, caseObj, index, isParent, parentLevel = 0, dataSetName, + handleAddAttribute, collectionsModel, handleSortAttribute, renameAttribute, hasFocusBeenSet, + editCaseValue, selectedDataSet } = props; + const { collections, attrVisibilities, attrPrecisions, attrTypes } = collectionsModel; + const collectionId = caseObj.collection.id; + const { focusSetForLevels, updateFocusSetForLevel } = useTableHeaderFocusContext(); + const { children, id, values } = caseObj; + const focusSetForLevel = !!parentLevel && focusSetForLevels.get(parentLevel); + const shouldGetFocusOnNewAttribute = !focusSetForLevel; + if (!focusSetForLevel) { + updateFocusSetForLevel?.(parentLevel || 0); + } + + if (!children.length) { + return ( + + + + ); + } else { + return ( + <> + {index === 0 && + + + {showHeaders ? ( + + {children[0].collection.name} + + ) : } + + } + + + + + + + {caseObj.children.map((child, i) => { + const nextProps: PortraitViewRowProps = { + ...props, + caseObj: child, + index: i, + isParent: true, + parentLevel: parentLevel + 1, + hasFocusBeenSet: hasFocusBeenSet || shouldGetFocusOnNewAttribute + }; + if (i === 0 && !child.children.length) { + return ( + + + + + + + ); + } else { + return ; + } + })} + +
+
+
+ + + ); + } +}); diff --git a/src/components/nested-table-view/portrait/portrait-table.tsx b/src/components/nested-table-view/portrait/portrait-table.tsx new file mode 100644 index 0000000..10cf407 --- /dev/null +++ b/src/components/nested-table-view/portrait/portrait-table.tsx @@ -0,0 +1,75 @@ +import React, { useRef } from "react"; +import { observer } from "mobx-react-lite"; +import { ICollection, ITableProps } from "../../../types"; +import { DraggableTableContainer } from "../common/draggable-table-tags"; +import { TableScrollTopContext, useTableScrollTop } from "../../../hooks/useTableScrollTop"; +import { AddAttributeButton } from "../common/add-attribute-button"; +import { TableHeaderFocusContext } from "../../../hooks/useTableHeaderFocusContext"; +import { PortraitTableRow } from "./portrait-table-row"; + +import css from "../common/tables.scss"; + +export const PortraitTable = observer(function PortraitView(props: ITableProps) { + const {collectionClasses, collectionsModel, selectedDataSet, getValueLength, dataSetName, + handleAddAttribute} = props; + const { collections } = collectionsModel; + const tableRef = useRef(null); + const tableScrollTop = useTableScrollTop(tableRef); + + const renderTable = () => { + const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; + const {className} = collectionClasses[0]; + const firstRowValues = parentColl.cases.map(caseObj => caseObj.values); + const valueCount = getValueLength(firstRowValues); + const focusSetForLevels = new Map(); + + const updateFocusSetForLevel = (level: number) => { + focusSetForLevels.set(level, true); + }; + + return ( + + + + + + + + + + + {parentColl.cases.map((caseObj, index) => ( + + ))} + +
{selectedDataSet.name}
+
+ {parentColl.name} + +
+
+
+
+ ); + }; + + return ( + +
+ {collections.length && collectionClasses.length && renderTable()} +
+
+ ); +}); diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx deleted file mode 100644 index 25c24a7..0000000 --- a/src/components/portrait-view.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React, { useRef } from "react"; -import { observer } from "mobx-react-lite"; -import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; -import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; -import { TableScrollTopContext, useTableScrollTop } from "../hooks/useTableScrollTop"; -import { getAttrPrecisions, getAttrTypes, getAttrVisibility } from "../utils/utils"; -import { AddAttributeButton } from "./add-attribute-button"; -import { TableHeaders } from "./table-headers"; -import { TableHeaderFocusContext, useTableHeaderFocusContext } from "../hooks/useTableHeaderFocusContext"; - -import css from "./tables.scss"; - -export type PortraitViewRowProps = {caseObj: IProcessedCaseObj, index?: null|number, - precisions: Record, - attrTypes: Record, - attrVisibilities: Record, - isParent: boolean, parentLevel?: number - dataSetName: string, hasFocusBeenSet?: boolean} & ITableProps; - -export const PortraitViewRow = observer(function PortraitViewRow(props: PortraitViewRowProps) { - const {paddingStyle, mapCellsFromValues, showHeaders, precisions, attrTypes, attrVisibilities, - getClassName, caseObj, index, isParent, parentLevel = 0, dataSetName, - handleAddAttribute, collections, handleSortAttribute, renameAttribute, - hasFocusBeenSet} = props; - const collectionId = caseObj.collection.id; - const {focusSetForLevels, updateFocusSetForLevel} = useTableHeaderFocusContext(); - const {children, id, values} = caseObj; - const focusSetForLevel = !!parentLevel && focusSetForLevels.get(parentLevel); - const shouldGetFocusOnNewAttribute = !focusSetForLevel; - if (!focusSetForLevel) { - updateFocusSetForLevel?.(parentLevel || 0); - } - - if (!children.length) { - return ( - - {mapCellsFromValues(collectionId, `row-${index}`, caseObj, precisions, attrTypes, attrVisibilities)} - - ); - } else { - return ( - <> - {index === 0 && - - - {showHeaders ? ( - - {children[0].collection.name} - - ) : } - - } - - {mapCellsFromValues( - collectionId, `parent-row-${index}`, caseObj, precisions, attrTypes, attrVisibilities, - isParent, parentLevel - )} - - - - - {caseObj.children.map((child, i) => { - const nextProps: PortraitViewRowProps = { - ...props, - caseObj: child, - index: i, - isParent, - parentLevel: parentLevel + 1, - hasFocusBeenSet: hasFocusBeenSet || shouldGetFocusOnNewAttribute - }; - if (i === 0 && !child.children.length) { - return ( - - - - - - - ); - } else { - return ; - } - })} - -
-
-
- - - ); - } -}); - -export const PortraitView = observer(function PortraitView(props: ITableProps) { - const {collectionClasses, selectedDataSet, collections, getValueLength, dataSetName, handleAddAttribute} = props; - const tableRef = useRef(null); - const tableScrollTop = useTableScrollTop(tableRef); - - const renderTable = () => { - const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; - const {className} = collectionClasses[0]; - const firstRowValues = parentColl.cases.map(caseObj => caseObj.values); - const valueCount = getValueLength(firstRowValues); - const precisions = getAttrPrecisions(collections); - const attrTypes = getAttrTypes(collections); - const attrVisibilities = getAttrVisibility(collections); - const focusSetForLevels = new Map(); - - const updateFocusSetForLevel = (level: number) => { - focusSetForLevels.set(level, true); - }; - - return ( - - - - - - - - - - - {parentColl.cases.map((caseObj, index) => ( - - ))} - -
{selectedDataSet.name}
-
- {parentColl.name} - -
-
-
-
- ); - }; - - return ( - -
- {collections.length && collectionClasses.length && renderTable()} -
-
- ); -}); diff --git a/src/components/table-cell.tsx b/src/components/table-cell.tsx deleted file mode 100644 index c94b16f..0000000 --- a/src/components/table-cell.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from "react"; -import { observer } from "mobx-react-lite"; -import { IResult } from "@concord-consortium/codap-plugin-api"; -import { DraggagleTableData } from "./draggable-table-tags"; -import { IProcessedCaseObj } from "../types"; - -interface IProps { - key: string; - index: number; - collectionId: number; - rowKey: string; - caseObj: IProcessedCaseObj; - attributeName: string; - cellValue: any; - precision: number; - attrType?: string|null; - isHidden: boolean; - isParent?: boolean; - selectedDataSet: string; - parentLevel?: number; - editCaseValue: (newValue: string, caseObj: IProcessedCaseObj, attrTitle: string) => Promise; -} - -export const TableCell: React.FC = observer(function TableCell(props) { - const { attributeName, cellValue, precision, attrType, isHidden, isParent, selectedDataSet, - collectionId, rowKey, caseObj, parentLevel, index, editCaseValue } = props; - - if (attributeName === "id" || (typeof cellValue !== "string" && typeof cellValue !== "number") || isHidden ) { - return null; - } - - let displayValue: string|number; - const isNumericType= attrType === "numeric"; - const hasValue= cellValue !== ""; - const parsedValue: number = typeof cellValue === "string" ? parseFloat(cellValue) : NaN; - const isNumber= !isNaN(parsedValue); - const hasPrecision= precision !== undefined; - const defaultValue: string | number = cellValue; - const isNumberType= typeof cellValue === "number"; - - if (isNumericType && hasValue && isNumber) { - const cellValAsNumber = Number(cellValue); - const isWholeNumber: boolean = cellValAsNumber % 1 === 0; - displayValue = isWholeNumber - ? parseInt(cellValue as string, 10) - : parsedValue.toFixed(hasPrecision ? precision : 2); - } else if (!isNumericType && isNumberType && hasValue) { - displayValue = (cellValue as number).toFixed(hasPrecision ? precision : 2); - } else { - displayValue = defaultValue; - } - - return ( - - {displayValue} - - ); -}); diff --git a/src/models/attributes.ts b/src/models/attributes.ts index e29bdf8..bb81b13 100644 --- a/src/models/attributes.ts +++ b/src/models/attributes.ts @@ -9,6 +9,7 @@ export const AttributeModel = types.model("AttributeModel", { id: types.number, name: types.string, renamable: types.optional(types.boolean, true), + precision: types.optional(types.number, 2), title: types.string, type: types.string, }); diff --git a/src/models/collections.ts b/src/models/collections.ts index 85f5f23..a856e56 100644 --- a/src/models/collections.ts +++ b/src/models/collections.ts @@ -34,6 +34,33 @@ export const CollectionsModel = types.model("CollectionsModel", { }); }); return result; + }, + get attrPrecisions() { + const precisions: Record = {}; + self.collections.forEach(collection => { + collection.attrs.forEach(attr => { + precisions[attr.name] = attr.precision; + }); + }); + return precisions; + }, + get attrTypes() { + const attrTypes: Record = {}; + self.collections.forEach(collection => { + collection.attrs.forEach(attr => { + attrTypes[attr.name] = attr.type; + }); + }); + return attrTypes; + }, + get attrVisibilities() { + const visibilities: Record = {}; + self.collections.forEach(collection => { + collection.attrs.forEach(attr => { + visibilities[attr.name] = attr.hidden; + }); + }); + return visibilities; } })); diff --git a/src/types.ts b/src/types.ts index 5bc87a2..3d58290 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { IResult } from "@concord-consortium/codap-plugin-api"; +import { CollectionsModelType } from "./models/collections"; import { ReactNode } from "react"; export type PropsWithChildren

= P & { children?: ReactNode | ReactNode[] }; @@ -71,11 +73,8 @@ export interface ITableProps { collectionClasses: Array; getClassName: (caseObj: IProcessedCaseObj) => string; selectedDataSet: IDataSet; - collections: ICollection[]; - mapCellsFromValues: (collectionId: number, rowKey: string, caseObj: IProcessedCaseObj, - precisions: Record, attrTypes: Record, - attrVisibilities: Record, isParent?: boolean, resizeCounter?: number, - parentLevel?: number) => ReactNode | ReactNode[]; + collectionsModel: CollectionsModelType; + editCaseValue: (newValue: string, caseObj: IProcessedCaseObj, attrTitle: string) => Promise; getValueLength: (firstRow: Array) => number; paddingStyle: Record; handleSortAttribute: (context: string, attrId: number, isDescending: boolean) => void; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 5083731..39c3a2c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,42 +1,30 @@ -import { IAttribute, ICollections } from "../types"; +import { IAttribute } from "../types"; -const getAllAttributesFromCollections = (collections: ICollections[]) => { - const attrArray: any[] = []; - collections.forEach((collection: any) => { - attrArray.push(...collection.attrs); - }); - return attrArray; -}; +export const getDisplayValue = (cellValue: string | number, attrTitle: string, + attrTypes: Record, precisions: Record) => { + let displayValue: string|number; + const isNumericType = attrTypes[attrTitle] === "numeric"; + const hasValue = cellValue !== ""; + const parsedValue: number = typeof cellValue === "string" ? parseFloat(cellValue) : NaN; + const isNumber = !isNaN(parsedValue); + const hasPrecision = precisions[attrTitle] !== undefined; + const defaultValue: string | number = cellValue; + const isNumberType = typeof cellValue === "number"; -export const getAttrPrecisions = (collections: any) => { - const attrs = getAllAttributesFromCollections(collections); - const precisions = attrs.reduce((acc: Record, attr: any) => { - const numPrecision = parseInt(attr.precision, 10); - acc[attr.name] = isNaN(numPrecision) ? 2 : numPrecision; - return acc; - }, {}); - return precisions; -}; + if (isNumericType && hasValue && isNumber) { + const cellValAsNumber = Number(cellValue); + const isWholeNumber: boolean = cellValAsNumber % 1 === 0; + displayValue = isWholeNumber + ? parseInt(cellValue as string, 10) + : parsedValue.toFixed(hasPrecision ? precisions[attrTitle] : 2); + } else if (!isNumericType && isNumberType && hasValue) { + displayValue = (cellValue as number).toFixed(hasPrecision ? precisions[attrTitle] : 2); + } else { + displayValue = defaultValue; + } -export const getAttrTypes = (collections: any) => { - const attrs = getAllAttributesFromCollections(collections); - const attrTypes = attrs.reduce( - (acc: Record, attr: any) => { - acc[attr.name] = attr.type || null; - return acc; - }, {}); - return attrTypes; -}; - -export const getAttrVisibility = (collections: any) => { - const attrs = getAllAttributesFromCollections(collections); - const attrVisibilities = attrs.reduce( - (acc: Record, attr: any) => { - acc[attr.name] = attr.hidden || false; - return acc; - }, {}); - return attrVisibilities; -}; + return `${displayValue}`; + }; export const newAttributeSlug = "newAttr";