From fa00db4da5459b72ba39e40493de807fc4d7a986 Mon Sep 17 00:00:00 2001 From: Cody O'Donnell Date: Tue, 13 Feb 2024 14:41:52 -0800 Subject: [PATCH 1/6] Update table cell styles --- src/common/colors.scss | 2 +- src/common/components/Table.module.scss | 26 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/common/colors.scss b/src/common/colors.scss index f4099d82..fb49d7b4 100755 --- a/src/common/colors.scss +++ b/src/common/colors.scss @@ -44,13 +44,13 @@ $kbase-palette: ( // graphite grey // Hex: #9D9389 HSL: 30, 9%, 58% "grey": rgb(157, 147, 137), + "light-gray": rgb(238, 238, 238), "white": rgb(255, 255, 255), "black": rgb(0, 0, 0), "ink": rgb(23, 20, 18), "neutral": rgb(106, 97, 88), "silver": rgb(192, 192, 192), - "light-gray": rgb(238, 238, 238), "base-lightest": rgb(242, 239, 235), "base-lighter": rgb(222, 213, 203), diff --git a/src/common/components/Table.module.scss b/src/common/components/Table.module.scss index 56099325..bcb9ee14 100644 --- a/src/common/components/Table.module.scss +++ b/src/common/components/Table.module.scss @@ -1,3 +1,5 @@ +/* stylelint-disable selector-max-compound-selectors -- Need to modify sort icon in table headers */ + @import "../colors"; $border: 1px solid use-color("base-lighter"); @@ -81,12 +83,30 @@ $header-height: 2em; } thead th { - background-color: use-color("primary"); - color: use-color("white"); + background-color: use-color("light-gray"); + color: use-color("base-darker"); + cursor: pointer; + font-size: 0.85rem; + font-weight: bold; height: $header-height; + text-align: left; + text-transform: uppercase; + transition: 0.25s; white-space: nowrap; } + thead th:hover { + color: use-color("base"); + } + + thead th > span:first-child { + margin-right: 0.25rem; + } + + thead th > span:first-child svg { + color: use-color("base-light"); + } + tfoot th { background-color: use-color("white"); color: use-color("primary-dark"); @@ -100,7 +120,7 @@ $header-height: 2em; border-top: $border; max-width: 20em; overflow: clip; - padding: 5px; + padding: 0.75rem 1rem; text-overflow: ellipsis; white-space: nowrap; } From 346db9260921fdc722ebd4650197230faa8cc661 Mon Sep 17 00:00:00 2001 From: Cody O'Donnell Date: Tue, 13 Feb 2024 16:31:23 -0800 Subject: [PATCH 2/6] Update collections sidebar --- src/common/components/Sidebar.module.scss | 5 +- src/common/components/Sidebar.tsx | 1 + .../collections/CollectionSidebar.tsx | 56 ++++++++----------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/common/components/Sidebar.module.scss b/src/common/components/Sidebar.module.scss index 46fbc0fb..63b62d8c 100644 --- a/src/common/components/Sidebar.module.scss +++ b/src/common/components/Sidebar.module.scss @@ -48,10 +48,11 @@ } .sidebar li.section-label { - color: use-color("base-light"); - font-size: 0.875rem; + color: use-color("base"); + font-size: 0.85rem; font-weight: bold; margin-top: 0.5rem; + text-transform: uppercase; } .sidebar li .item-icon { diff --git a/src/common/components/Sidebar.tsx b/src/common/components/Sidebar.tsx index 3a770a4c..f96ef1e3 100644 --- a/src/common/components/Sidebar.tsx +++ b/src/common/components/Sidebar.tsx @@ -4,6 +4,7 @@ import { FC, ReactElement } from 'react'; import { Link } from 'react-router-dom'; export interface SidebarItem { + id: string; displayText: string; pathname?: string; isSelected?: boolean; diff --git a/src/features/collections/CollectionSidebar.tsx b/src/features/collections/CollectionSidebar.tsx index d111d602..910410b6 100644 --- a/src/features/collections/CollectionSidebar.tsx +++ b/src/features/collections/CollectionSidebar.tsx @@ -3,19 +3,10 @@ import { FC } from 'react'; import { snakeCaseToHumanReadable } from '../../common/utils/stringUtils'; import { Sidebar, SidebarItem } from '../../common/components/Sidebar'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faDna, - faArrowLeft, - faVial, - faChartBar, - faTableCells, - faMicroscope, - faList, -} from '@fortawesome/free-solid-svg-icons'; +import { faDna, faArrowLeft, faVial } from '@fortawesome/free-solid-svg-icons'; import { Button } from '../../common/components/Button'; import { useNavigate } from 'react-router-dom'; import classes from './Collections.module.scss'; -import { IconProp } from '@fortawesome/fontawesome-svg-core'; /** * List of data product ids that should be grouped into the @@ -23,8 +14,8 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; */ const genomesDataProducts = [ 'genome_attribs', - 'microtrait', 'taxa_count', + 'microtrait', 'biolog', ]; @@ -34,22 +25,6 @@ const genomesDataProducts = [ */ const samplesDataProducts = ['samples']; -interface DataProductIconMap { - [id: string]: IconProp; -} - -/** - * Map data product ids to an icon component - */ -const dataProductIcon: DataProductIconMap = { - overview: faList, - genome_attribs: faDna, - microtrait: faTableCells, - taxa_count: faChartBar, - biolog: faMicroscope, - samples: faVial, -}; - /** * Implementation of the Sidebar component for the CollectionDetail pages. * Takes a collection and renders its data products into a navigation sidebar. @@ -64,11 +39,11 @@ export const CollectionSidebar: FC<{ const genomesItems: SidebarItem[] = []; const samplesItems: SidebarItem[] = []; - collection.data_products?.forEach((dp) => { + collection.data_products.forEach((dp) => { const dpItem = { + id: dp.product, displayText: snakeCaseToHumanReadable(dp.product), pathname: `/collections/${collection.id}/${dp.product}`, - icon: , isSelected: !showOverview && currDataProduct === dp, }; if (genomesDataProducts.indexOf(dp.product) > -1) { @@ -78,10 +53,26 @@ export const CollectionSidebar: FC<{ } }); + // Enforce a certain order for the genomes sidebar items based on genomesDataProducts array + genomesItems.sort((a, b) => { + return ( + genomesDataProducts.indexOf(a.id) - genomesDataProducts.indexOf(b.id) + ); + }); + + // Enforce a certain order for the samples sidebar items based on samplesDataProducts array + samplesItems.sort((a, b) => { + return ( + samplesDataProducts.indexOf(a.id) - samplesDataProducts.indexOf(b.id) + ); + }); + // First item in genomeItems should be a section label if (genomesItems.length > 0) { genomesItems.unshift({ + id: 'geneomes_section', displayText: 'Genomes', + icon: , isSectionLabel: true, }); } @@ -89,18 +80,19 @@ export const CollectionSidebar: FC<{ // First item in samplesItems should be a section label if (samplesItems.length > 0) { samplesItems.unshift({ + id: 'samples_section', displayText: 'Samples', + icon: , isSectionLabel: true, }); } const items: SidebarItem[] = [ - // Right now the Overview item is added manually - // In the future this could be part of collection.data_products + // Add Overview item to the top of the sidebar list { + id: 'overview', displayText: 'Overview', pathname: `/collections/${collection.id}/`, - icon: , isSelected: showOverview, }, ...genomesItems, From 5fdc26893ce9ba5e87f2498e8e136064c202f95b Mon Sep 17 00:00:00 2001 From: Cody O'Donnell Date: Tue, 13 Feb 2024 16:40:52 -0800 Subject: [PATCH 3/6] Update active sidebar item style --- src/common/components/Sidebar.module.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/components/Sidebar.module.scss b/src/common/components/Sidebar.module.scss index 63b62d8c..0e61561a 100644 --- a/src/common/components/Sidebar.module.scss +++ b/src/common/components/Sidebar.module.scss @@ -45,6 +45,8 @@ .sidebar li.sidebar-item.selected > .sidebar-item-inner { background-color: use-color("silver"); + color: use-color("base-darker"); + font-weight: 600; } .sidebar li.section-label { From 1aa694a07a66e10c82f25245abc9b8535f1b3625 Mon Sep 17 00:00:00 2001 From: Cody O'Donnell Date: Tue, 13 Feb 2024 16:56:48 -0800 Subject: [PATCH 4/6] Add display names to sidebar items --- .../collections/CollectionSidebar.tsx | 66 ++++++++++++------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/features/collections/CollectionSidebar.tsx b/src/features/collections/CollectionSidebar.tsx index 910410b6..e42549a0 100644 --- a/src/features/collections/CollectionSidebar.tsx +++ b/src/features/collections/CollectionSidebar.tsx @@ -9,22 +9,37 @@ import { useNavigate } from 'react-router-dom'; import classes from './Collections.module.scss'; /** - * List of data product ids that should be grouped into the - * "Genomes" category. + * List of data products with associated metadata and listed + * in the order they should appear in the sidebar. */ -const genomesDataProducts = [ - 'genome_attribs', - 'taxa_count', - 'microtrait', - 'biolog', +const dataProductsMeta = [ + { + product: 'genome_attribs', + displayName: 'Genome Attributes', + section: 'Genomes', + }, + { + product: 'taxa_count', + displayName: 'Taxa Count', + section: 'Genomes', + }, + { + product: 'microtrait', + displayName: 'Microtrait', + section: 'Genomes', + }, + { + product: 'biolog', + displayName: 'Biolog', + section: 'Genomes', + }, + { + product: 'samples', + displayName: 'Sample Attributes', + section: 'Samples', + }, ]; -/** - * List of data product ids that should be grouped into the - * "Samples" category. - */ -const samplesDataProducts = ['samples']; - /** * Implementation of the Sidebar component for the CollectionDetail pages. * Takes a collection and renders its data products into a navigation sidebar. @@ -38,33 +53,36 @@ export const CollectionSidebar: FC<{ const navigate = useNavigate(); const genomesItems: SidebarItem[] = []; const samplesItems: SidebarItem[] = []; + const genomeProducts = dataProductsMeta + .filter((d) => d.section === 'Genomes') + .map((d) => d.product); + const sampleProducts = dataProductsMeta + .filter((d) => d.section === 'Samples') + .map((d) => d.product); collection.data_products.forEach((dp) => { + const dpMeta = dataProductsMeta.find((d) => d.product === dp.product); const dpItem = { id: dp.product, - displayText: snakeCaseToHumanReadable(dp.product), + displayText: dpMeta?.displayName || snakeCaseToHumanReadable(dp.product), pathname: `/collections/${collection.id}/${dp.product}`, isSelected: !showOverview && currDataProduct === dp, }; - if (genomesDataProducts.indexOf(dp.product) > -1) { + if (genomeProducts.indexOf(dp.product) > -1) { genomesItems.push(dpItem); - } else if (samplesDataProducts.indexOf(dp.product) > -1) { + } else if (sampleProducts.indexOf(dp.product) > -1) { samplesItems.push(dpItem); } }); - // Enforce a certain order for the genomes sidebar items based on genomesDataProducts array + // Enforce a certain order for the genomes sidebar items based on genomeProducts array genomesItems.sort((a, b) => { - return ( - genomesDataProducts.indexOf(a.id) - genomesDataProducts.indexOf(b.id) - ); + return genomeProducts.indexOf(a.id) - genomeProducts.indexOf(b.id); }); - // Enforce a certain order for the samples sidebar items based on samplesDataProducts array + // Enforce a certain order for the samples sidebar items based on sampleProducts array samplesItems.sort((a, b) => { - return ( - samplesDataProducts.indexOf(a.id) - samplesDataProducts.indexOf(b.id) - ); + return sampleProducts.indexOf(a.id) - sampleProducts.indexOf(b.id); }); // First item in genomeItems should be a section label From e735d64d2b171a2653302424d76bbd69be309fc3 Mon Sep 17 00:00:00 2001 From: David Lyon Date: Wed, 14 Feb 2024 11:02:31 -0800 Subject: [PATCH 5/6] Add columnMeta to state and use column displayname in genome attribs table header --- src/common/api/collectionsApi.ts | 57 ++++++++++--------- src/common/components/Table.test.tsx | 2 +- src/common/components/Table.tsx | 28 ++++----- src/features/collections/CollectionDetail.tsx | 6 +- src/features/collections/collectionsSlice.ts | 49 ++++++++++++++-- .../data_products/GenomeAttribs.tsx | 8 ++- .../data_products/SampleAttribs.tsx | 2 +- 7 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/common/api/collectionsApi.ts b/src/common/api/collectionsApi.ts index 8e669d9e..056dc285 100644 --- a/src/common/api/collectionsApi.ts +++ b/src/common/api/collectionsApi.ts @@ -168,6 +168,35 @@ const isCollectionsError = (data: unknown): data is CollectionsError => { return true; }; +export type ColumnMeta = { + key: string; + category?: string; + description?: string; + display_name?: string; +} & ( + | { + type: 'date' | 'int' | 'float'; + filter_strategy: undefined; + min_value: number; + max_value: number; + enum_values: undefined; + } + | { + type: 'string'; + filter_strategy: 'fulltext' | 'prefix' | 'identity' | 'ngram'; + min_value: undefined; + max_value: undefined; + enum_values: undefined; + } + | { + type: 'enum'; + filter_strategy: undefined; + min_value: undefined; + max_value: undefined; + enum_values: string[]; + } +); + interface CollectionsResults { status: { service_name: string; @@ -214,33 +243,7 @@ interface CollectionsResults { }; getGenomeAttribsMeta: { count: number; - columns: Array< - { - key: string; - } & ( - | { - type: 'date' | 'int' | 'float'; - filter_strategy: undefined; - min_value: number; - max_value: number; - enum_values: undefined; - } - | { - type: 'string'; - filter_strategy: 'fulltext' | 'prefix' | 'identity' | 'ngram'; - min_value: undefined; - max_value: undefined; - enum_values: undefined; - } - | { - type: 'enum'; - filter_strategy: undefined; - min_value: undefined; - max_value: undefined; - enum_values: string[]; - } - ) - >; + columns: Array; }; getMicroTrait: { description: string; diff --git a/src/common/components/Table.test.tsx b/src/common/components/Table.test.tsx index c342af36..6de0f8b2 100644 --- a/src/common/components/Table.test.tsx +++ b/src/common/components/Table.test.tsx @@ -384,7 +384,7 @@ test('useTableColumns hook makes appropriate headers from string lists', () => { const colSpy = jest.fn(); const Wrapper = () => { const cols = useTableColumns({ - fieldNames: ['a', 'b', 'c', 'd', 'q', 'x'], + fields: ['a', 'b', 'c', 'd', 'q', 'x'].map((id) => ({ id })), exclude: ['b', 'z'], order: ['c', 'a', 'q'], }); diff --git a/src/common/components/Table.tsx b/src/common/components/Table.tsx index 0e4eaa67..f95b2ea9 100644 --- a/src/common/components/Table.tsx +++ b/src/common/components/Table.tsx @@ -291,11 +291,11 @@ const someHeaderDefines = ( }; export const useTableColumns = ({ - fieldNames = [], + fields = [], order = [], exclude = [], }: { - fieldNames?: string[]; + fields?: { displayName?: string; id: string }[]; order?: string[]; exclude?: string[]; }) => { @@ -304,15 +304,15 @@ export const useTableColumns = ({ rowData: RowData ) => RowData[number]; } = {}; - fieldNames.forEach((fieldName, index) => { - accessors[fieldName] = (rowData) => rowData[index]; + fields.forEach(({ id }, index) => { + accessors[id] = (rowData) => rowData[index]; }); - const fieldsOrdered = fieldNames - .filter((name) => !exclude.includes(name)) + const fieldsOrdered = fields + .filter(({ id }) => !exclude.includes(id)) .sort((a, b) => { - const aOrder = order.indexOf(a); - const bOrder = order.indexOf(b); + const aOrder = order.indexOf(a.id); + const bOrder = order.indexOf(b.id); if (aOrder !== -1 && bOrder !== -1) { return aOrder - bOrder; } else if (aOrder !== -1) { @@ -320,23 +320,23 @@ export const useTableColumns = ({ } else if (bOrder !== -1) { return 1; } else { - return fieldNames.indexOf(a) - fieldNames.indexOf(b); + return fields.indexOf(a) - fields.indexOf(b); } }); return useMemo( () => { const columns = createColumnHelper(); - return fieldsOrdered.map((fieldName) => - columns.accessor(accessors[fieldName], { - header: fieldName.replace(/_/g, ' ').trim(), - id: fieldName, + return fieldsOrdered.map((field) => + columns.accessor(accessors[field.id], { + header: field.displayName ?? field.id.replace(/_/g, ' ').trim(), + id: field.id, }) ); }, // We only want to remake the columns if fieldNames or fieldsOrdered have new values // eslint-disable-next-line react-hooks/exhaustive-deps - [JSON.stringify(fieldNames), JSON.stringify(fieldsOrdered)] + [JSON.stringify(fields), JSON.stringify(fieldsOrdered)] ); }; diff --git a/src/features/collections/CollectionDetail.tsx b/src/features/collections/CollectionDetail.tsx index d16895fc..edb790fc 100644 --- a/src/features/collections/CollectionDetail.tsx +++ b/src/features/collections/CollectionDetail.tsx @@ -24,7 +24,8 @@ import { Loader } from '../../common/components/Loader'; import { CollectionSidebar } from './CollectionSidebar'; import { clearFilter, - clearFilters, + clearFiltersAndColumnMeta, + setColumnMeta, FilterState, setFilter, useCurrentSelection, @@ -271,9 +272,10 @@ const useCollectionFilters = (collectionId: string | undefined) => { ); useEffect(() => { if (collectionId && filterData) { - dispatch(clearFilters([collectionId, context])); + dispatch(clearFiltersAndColumnMeta([collectionId, context])); filterData.columns.forEach((column) => { const current = filters && filters[column.key]; + dispatch(setColumnMeta([collectionId, context, column.key, column])); if ( column.type === 'date' || column.type === 'float' || diff --git a/src/features/collections/collectionsSlice.ts b/src/features/collections/collectionsSlice.ts index 682afaa6..52d3233c 100644 --- a/src/features/collections/collectionsSlice.ts +++ b/src/features/collections/collectionsSlice.ts @@ -1,6 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { useEffect, useMemo } from 'react'; -import { createSelection, getSelection } from '../../common/api/collectionsApi'; +import { + ColumnMeta, + createSelection, + getSelection, +} from '../../common/api/collectionsApi'; import { useAppDispatch, useAppSelector, @@ -50,6 +54,11 @@ interface ClnState { [columnName: string]: FilterState; }; }; + columnMeta: { + [context: string]: { + [columnName: string]: ColumnMeta; + }; + }; } interface CollectionsState { @@ -65,6 +74,7 @@ const initialCollection: ClnState = { filterMatch: false, filterSelection: false, filters: {}, + columnMeta: {}, }; const initialState: CollectionsState = { @@ -128,6 +138,23 @@ export const CollectionSlice = createSlice({ cln.filterContext = defaultFilterContext; } }, + setColumnMeta: ( + state, + { + payload: [collectionId, context, columnName, columnMeta], + }: PayloadAction< + [ + collectionId: string, + context: string, + columnName: string, + columnMeta: ColumnMeta + ] + > + ) => { + const cln = collectionState(state, collectionId); + if (!cln.columnMeta[context]) cln.columnMeta[context] = {}; + cln.columnMeta[context][columnName] = columnMeta; + }, setFilter: ( state, { @@ -177,7 +204,7 @@ export const CollectionSlice = createSlice({ delete cln.filters[context][columnName].value; } }, - clearFilters: ( + clearFiltersAndColumnMeta: ( state, { payload: [collectionId, context], @@ -185,6 +212,7 @@ export const CollectionSlice = createSlice({ ) => { const cln = collectionState(state, collectionId); cln.filters[context] = {}; + cln.columnMeta[context] = {}; }, }, }); @@ -214,7 +242,8 @@ export const { setFilterContext, setFilter, clearFilter, - clearFilters, + clearFiltersAndColumnMeta, + setColumnMeta, setFilterMatch, setFilterSelection, } = CollectionSlice.actions; @@ -339,6 +368,11 @@ export const useFilters = (collectionId: string | undefined) => { ? state.collections.clns[collectionId]?.filterSelection : undefined ); + const columnMeta = useAppSelector((state) => + collectionId + ? state.collections.clns[collectionId]?.columnMeta?.[context] + : undefined + ); const formattedFilters = Object.entries(filters ?? {}) .filter(([column, filterState]) => Boolean(filterState.value)) @@ -387,5 +421,12 @@ export const useFilters = (collectionId: string | undefined) => { // eslint-disable-next-line react-hooks/exhaustive-deps [changeIndicator] ); - return { filterParams, context, filters, filterMatch, filterSelection }; + return { + filterParams, + context, + filters, + filterMatch, + filterSelection, + columnMeta, + }; }; diff --git a/src/features/collections/data_products/GenomeAttribs.tsx b/src/features/collections/data_products/GenomeAttribs.tsx index c2d705bf..7d8244a2 100644 --- a/src/features/collections/data_products/GenomeAttribs.tsx +++ b/src/features/collections/data_products/GenomeAttribs.tsx @@ -40,7 +40,8 @@ export const GenomeAttribs: FC<{ const matchId = useMatchId(collection_id); const selectionId = useSelectionId(collection_id); // get the shared filter state - const { filterMatch, filterSelection } = useFilters(collection_id); + const { filterMatch, filterSelection, columnMeta } = + useFilters(collection_id); const [sorting, setSorting] = useState([]); const requestSort = useMemo(() => { @@ -158,7 +159,10 @@ export const GenomeAttribs: FC<{ data: data?.table || [], getRowId: (row) => String(row[idIndex]), columns: useTableColumns({ - fieldNames: data?.fields.map((field) => field.name), + fields: data?.fields.map((field) => ({ + id: field.name, + displayName: columnMeta?.[field.name]?.display_name ?? field.name, + })), order: ['kbase_display_name', 'genome_size'], exclude: ['__match__', '__sel__'], }), diff --git a/src/features/collections/data_products/SampleAttribs.tsx b/src/features/collections/data_products/SampleAttribs.tsx index 4d1edd85..9a87ffa4 100644 --- a/src/features/collections/data_products/SampleAttribs.tsx +++ b/src/features/collections/data_products/SampleAttribs.tsx @@ -166,7 +166,7 @@ export const SampleAttribs: FC<{ data: data?.table || [], getRowId: (row) => rowId(row), columns: useTableColumns({ - fieldNames: data?.fields.map((field) => field.name), + fields: data?.fields.map((field) => ({ id: field.name })), order: ['kbase_id', 'kbase_sample_id'], exclude: ['__match__', '__sel__'], }), From adb3da825b1640f3d0c2d7600e3f1243470034c0 Mon Sep 17 00:00:00 2001 From: David Lyon Date: Wed, 14 Feb 2024 11:24:26 -0800 Subject: [PATCH 6/6] Align columns based on type --- src/common/components/Table.tsx | 21 ++++++++++++++++++- .../data_products/GenomeAttribs.tsx | 7 +++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/common/components/Table.tsx b/src/common/components/Table.tsx index f95b2ea9..43d91d18 100644 --- a/src/common/components/Table.tsx +++ b/src/common/components/Table.tsx @@ -22,6 +22,10 @@ import { CheckBox } from './CheckBox'; import { Loader } from './Loader'; import { HeatMapRow } from '../api/collectionsApi'; +type ColumnOptions = { + textAlign?: 'left' | 'right' | 'center'; +}; + export const Table = ({ table, className = '', @@ -74,6 +78,11 @@ export const Table = ({ + )?.textAlign, + }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} @@ -209,6 +218,11 @@ const TableHeader = ({ table }: { table: TableType }) => ( key={header.id} onClick={header.column.getToggleSortingHandler()} colSpan={header.colSpan} + style={{ + textAlign: ( + header.column.columnDef.meta as Partial + )?.textAlign, + }} > {!header.isPlaceholder && header.column.getCanSort() ? ( { @@ -331,6 +349,7 @@ export const useTableColumns = ({ columns.accessor(accessors[field.id], { header: field.displayName ?? field.id.replace(/_/g, ' ').trim(), id: field.id, + meta: field.options, }) ); }, diff --git a/src/features/collections/data_products/GenomeAttribs.tsx b/src/features/collections/data_products/GenomeAttribs.tsx index 7d8244a2..f204b32a 100644 --- a/src/features/collections/data_products/GenomeAttribs.tsx +++ b/src/features/collections/data_products/GenomeAttribs.tsx @@ -162,6 +162,13 @@ export const GenomeAttribs: FC<{ fields: data?.fields.map((field) => ({ id: field.name, displayName: columnMeta?.[field.name]?.display_name ?? field.name, + options: { + textAlign: ['float', 'int'].includes( + columnMeta?.[field.name]?.type ?? '' + ) + ? 'right' + : 'left', + }, })), order: ['kbase_display_name', 'genome_size'], exclude: ['__match__', '__sel__'],