@databiosphere/findable-ui 27.0.0 → 28.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +24 -0
  3. package/lib/components/Index/components/EntitiesView/components/ChartView/chartView.js +1 -1
  4. package/lib/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/plot.js +4 -2
  5. package/lib/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/utils.d.ts +21 -0
  6. package/lib/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/utils.js +34 -0
  7. package/lib/components/Index/components/EntitiesView/components/EntityList/entityList.d.ts +2 -1
  8. package/lib/components/Index/components/EntitiesView/components/EntityList/entityList.js +3 -5
  9. package/lib/components/Index/components/EntitiesView/components/EntityList/types.d.ts +4 -1
  10. package/lib/components/Index/components/Hero/components/Summaries/stories/summaries.stories.d.ts +6 -0
  11. package/lib/components/Index/components/Hero/components/Summaries/{summaries.stories.js → stories/summaries.stories.js} +2 -6
  12. package/lib/components/Index/components/Hero/stories/hero.stories.d.ts +6 -0
  13. package/lib/components/Index/components/Hero/stories/hero.stories.js +16 -0
  14. package/lib/components/Index/index.d.ts +1 -1
  15. package/lib/components/Index/index.js +6 -2
  16. package/lib/components/Index/stories/index.stories.d.ts +6 -0
  17. package/lib/components/Index/stories/index.stories.js +17 -0
  18. package/lib/components/Index/table/coreOptions/columns/cellFactory.d.ts +9 -0
  19. package/lib/components/Index/table/coreOptions/columns/cellFactory.js +13 -0
  20. package/lib/components/Index/table/hook.d.ts +3 -0
  21. package/lib/components/Index/table/hook.js +166 -0
  22. package/lib/components/Index/table/types.d.ts +4 -0
  23. package/lib/components/Index/table/types.js +1 -0
  24. package/lib/components/Index/types.d.ts +6 -2
  25. package/lib/components/Table/table.d.ts +5 -20
  26. package/lib/components/Table/table.js +10 -138
  27. package/lib/components/TableCreator/tableCreator.d.ts +5 -7
  28. package/lib/components/TableCreator/tableCreator.js +3 -35
  29. package/lib/views/ExploreView/exploreView.js +1 -3
  30. package/package.json +1 -1
  31. package/src/components/Index/components/EntitiesView/components/ChartView/chartView.tsx +1 -1
  32. package/src/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/plot.ts +5 -1
  33. package/src/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/utils.ts +43 -0
  34. package/src/components/Index/components/EntitiesView/components/EntityList/entityList.tsx +7 -14
  35. package/src/components/Index/components/EntitiesView/components/EntityList/types.ts +5 -1
  36. package/src/components/Index/components/Hero/components/Summaries/{summaries.stories.tsx → stories/summaries.stories.tsx} +4 -8
  37. package/src/components/Index/components/Hero/stories/hero.stories.tsx +22 -0
  38. package/src/components/Index/index.tsx +21 -3
  39. package/src/components/Index/stories/index.stories.tsx +23 -0
  40. package/src/components/Index/table/coreOptions/columns/cellFactory.tsx +22 -0
  41. package/src/components/Index/table/hook.ts +234 -0
  42. package/src/components/Index/table/types.ts +5 -0
  43. package/src/components/Index/types.ts +6 -2
  44. package/src/components/Table/table.tsx +16 -199
  45. package/src/components/TableCreator/tableCreator.tsx +7 -79
  46. package/src/views/ExploreView/exploreView.tsx +4 -10
  47. package/tests/chart.test.tsx +19 -8
  48. package/tests/chartView.test.tsx +1 -1
  49. package/lib/components/Index/components/Hero/components/Summaries/summaries.stories.d.ts +0 -13
  50. package/lib/components/Index/components/Hero/hero.stories.d.ts +0 -23
  51. package/lib/components/Index/components/Hero/hero.stories.js +0 -22
  52. package/lib/components/Index/index.stories.d.ts +0 -6
  53. package/lib/components/Index/index.stories.js +0 -26
  54. package/src/components/Index/components/Hero/hero.stories.tsx +0 -28
  55. package/src/components/Index/index.stories.tsx +0 -31
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "27.0.0"
2
+ ".": "28.0.1"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [28.0.1](https://github.com/DataBiosphere/findable-ui/compare/v28.0.0...v28.0.1) (2025-04-30)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * fix observable plot y-scale warning caused by numeric strings ([#449](https://github.com/DataBiosphere/findable-ui/issues/449)) ([#450](https://github.com/DataBiosphere/findable-ui/issues/450)) ([53df97a](https://github.com/DataBiosphere/findable-ui/commit/53df97a8508c822aef48aac4c71646cfddc0b605))
9
+
10
+ ## [28.0.0](https://github.com/DataBiosphere/findable-ui/compare/v27.0.0...v28.0.0) (2025-04-30)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * chart view - for client side filtering ([#447](https://github.com/DataBiosphere/findable-ui/issues/447)) (#448)
16
+
17
+ ### Features
18
+
19
+ * chart view - change 'per' to 'by' in chart titles ([#442](https://github.com/DataBiosphere/findable-ui/issues/442)) ([#445](https://github.com/DataBiosphere/findable-ui/issues/445)) ([52d2cc9](https://github.com/DataBiosphere/findable-ui/commit/52d2cc94129d0c3883b2f6c6c3b6b6eeb36d995c))
20
+ * chart view add percentage to counts ([#441](https://github.com/DataBiosphere/findable-ui/issues/441)) ([#443](https://github.com/DataBiosphere/findable-ui/issues/443)) ([ca609bb](https://github.com/DataBiosphere/findable-ui/commit/ca609bba7fb6d55a2b4112ac989bed1726c72872))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * chart view - for client side filtering ([#447](https://github.com/DataBiosphere/findable-ui/issues/447)) ([#448](https://github.com/DataBiosphere/findable-ui/issues/448)) ([d35ffb1](https://github.com/DataBiosphere/findable-ui/commit/d35ffb16aaf8fca967430f1abf1293927c6873f2))
26
+
3
27
  ## [27.0.0](https://github.com/DataBiosphere/findable-ui/compare/v26.0.0...v27.0.0) (2025-04-24)
4
28
 
5
29
 
@@ -14,7 +14,7 @@ export const ChartView = ({ categoryFilters, entityName, loading, testId, }) =>
14
14
  React.createElement(StyledGrid, { "data-testid": testId, ref: chartViewRef }, selectCategoryViews.map(({ key, label, values }) => (React.createElement(StyledGridPaperSection, { key: key },
15
15
  React.createElement(Typography, { variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_HEADING_SMALL },
16
16
  entityName,
17
- " per ",
17
+ " by ",
18
18
  label),
19
19
  React.createElement(Chart, { selectCategoryValueViews: values, width: width })))))));
20
20
  };
@@ -2,9 +2,10 @@ import * as Plot from "@observablehq/plot";
2
2
  import { PALETTE } from "../../../../../../../../../styles/common/constants/palette";
3
3
  import { formatCountSize } from "../../../../../../../../../utils/formatCountSize";
4
4
  import { DATA_FIELD, MARGIN_LEFT, TEXT_PADDING } from "./constants";
5
- import { getCategoryValueText, getCategoryValueTextFill, getColorRangeValue, getCountTextFill, getPlotHeight, getTicks, getXDomain, getYPaddingInner, getYPaddingOuter, isAnyValueSelected, renderText, } from "./utils";
5
+ import { getCategoryTotalCount, getCategoryValueText, getCategoryValueTextFill, getColorRangeValue, getCountText, getCountTextFill, getPlotHeight, getTicks, getXDomain, getYPaddingInner, getYPaddingOuter, isAnyValueSelected, renderText, } from "./utils";
6
6
  export function getPlotOptions(selectCategoryValueViews, width) {
7
7
  const isCategorySelected = isAnyValueSelected(selectCategoryValueViews);
8
+ const totalCount = getCategoryTotalCount(selectCategoryValueViews);
8
9
  return {
9
10
  color: {
10
11
  domain: [false, true], // false = unselected, true = selected.
@@ -59,7 +60,7 @@ export function getPlotOptions(selectCategoryValueViews, width) {
59
60
  fontWeight: 500,
60
61
  lineHeight: 0.8125,
61
62
  render: renderText,
62
- text: DATA_FIELD.COUNT,
63
+ text: (d) => getCountText(d, totalCount),
63
64
  textAnchor: "end",
64
65
  x: DATA_FIELD.COUNT,
65
66
  y: DATA_FIELD.LABEL,
@@ -84,6 +85,7 @@ export function getPlotOptions(selectCategoryValueViews, width) {
84
85
  paddingOuter: getYPaddingOuter(),
85
86
  tickFormat: () => "",
86
87
  tickSize: 0,
88
+ type: "band", // Treats number-like strings as categorical labels to prevent numeric scale warnings.
87
89
  },
88
90
  };
89
91
  }
@@ -1,5 +1,11 @@
1
1
  import { ChannelValues, Context, Dimensions, RenderFunction, ScaleFunctions } from "@observablehq/plot";
2
2
  import { SelectCategoryValueView } from "../../../../../../../../../common/entities";
3
+ /**
4
+ * Calculates the total count of all category values from the provided array of category value views.
5
+ * @param selectCategoryValueViews - An array of objects representing category values, where each object contains a count property.
6
+ * @returns The total sum of the count property from all category value objects in the array.
7
+ */
8
+ export declare function getCategoryTotalCount(selectCategoryValueViews: SelectCategoryValueView[]): number;
3
9
  /**
4
10
  * Returns the text for the category value point.
5
11
  * @param d - Data point.
@@ -19,6 +25,21 @@ export declare function getCategoryValueTextFill(d: SelectCategoryValueView, isC
19
25
  * @returns Color range value.
20
26
  */
21
27
  export declare function getColorRangeValue(isCategorySelected: boolean): string;
28
+ /**
29
+ * Calculates the percentage representation of a count value relative to a total.
30
+ * @param d - Data point containing the count value to calculate the percentage for.
31
+ * @param total - The total value used as the denominator to compute the percentage.
32
+ * @returns The calculated percentage value, rounded to one decimal place.
33
+ */
34
+ export declare function getCountPercentage(d: SelectCategoryValueView, total: number): string;
35
+ /**
36
+ * Returns the text for the count point.
37
+ * Includes the count and its percentage of the total.
38
+ * @param d - Data point.
39
+ * @param total - Total count.
40
+ * @returns Text string.
41
+ */
42
+ export declare function getCountText(d: SelectCategoryValueView, total: number): string;
22
43
  /**
23
44
  * Returns the fill color for the count point.
24
45
  * @param d - Data point.
@@ -1,5 +1,13 @@
1
1
  import { PALETTE } from "../../../../../../../../../styles/common/constants/palette";
2
2
  import { BAR_GAP, BAR_HEIGHT, MARGIN_LEFT, TEXT_PADDING, TICKS, } from "./constants";
3
+ /**
4
+ * Calculates the total count of all category values from the provided array of category value views.
5
+ * @param selectCategoryValueViews - An array of objects representing category values, where each object contains a count property.
6
+ * @returns The total sum of the count property from all category value objects in the array.
7
+ */
8
+ export function getCategoryTotalCount(selectCategoryValueViews) {
9
+ return selectCategoryValueViews.reduce((acc, { count }) => acc + count, 0);
10
+ }
3
11
  /**
4
12
  * Returns the text for the category value point.
5
13
  * @param d - Data point.
@@ -31,6 +39,32 @@ export function getCategoryValueTextFill(d, isCategorySelected) {
31
39
  export function getColorRangeValue(isCategorySelected) {
32
40
  return isCategorySelected ? PALETTE.SMOKE_LIGHT : "#C5E3FC";
33
41
  }
42
+ /**
43
+ * Calculates the percentage representation of a count value relative to a total.
44
+ * @param d - Data point containing the count value to calculate the percentage for.
45
+ * @param total - The total value used as the denominator to compute the percentage.
46
+ * @returns The calculated percentage value, rounded to one decimal place.
47
+ */
48
+ export function getCountPercentage(d, total) {
49
+ if (total === 0)
50
+ return "0";
51
+ const percentage = (d.count / total) * 100;
52
+ const roundedPercentage = Math.round(percentage * 10) / 10;
53
+ if (roundedPercentage < 0.1)
54
+ return "< 0.1";
55
+ // Round to one decimal place.
56
+ return roundedPercentage.toFixed(1);
57
+ }
58
+ /**
59
+ * Returns the text for the count point.
60
+ * Includes the count and its percentage of the total.
61
+ * @param d - Data point.
62
+ * @param total - Total count.
63
+ * @returns Text string.
64
+ */
65
+ export function getCountText(d, total) {
66
+ return `${d.count.toLocaleString()} (${getCountPercentage(d, total)}%)`;
67
+ }
34
68
  /**
35
69
  * Returns the fill color for the count point.
36
70
  * @param d - Data point.
@@ -1,2 +1,3 @@
1
+ import { RowData } from "@tanstack/react-table";
1
2
  import { EntityListProps } from "./types";
2
- export declare const EntityList: ({ entityListType, }: EntityListProps) => JSX.Element | null;
3
+ export declare const EntityList: <T extends RowData>({ entityListType, loading, table, }: EntityListProps<T>) => JSX.Element | null;
@@ -2,15 +2,13 @@ import React from "react";
2
2
  import { useConfig } from "../../../../../../hooks/useConfig";
3
3
  import { useExploreState } from "../../../../../../hooks/useExploreState";
4
4
  import { TableCreator } from "../../../../../TableCreator/tableCreator";
5
- export const EntityList = ({ entityListType, }) => {
5
+ export const EntityList = ({ entityListType, loading, table, }) => {
6
6
  const { entityConfig } = useConfig();
7
7
  const { exploreState } = useExploreState();
8
- const { listItems, loading } = exploreState;
9
- const { getId: getRowId, list, listView } = entityConfig;
10
- const { columns: columnsConfig } = list;
8
+ const { listView } = entityConfig;
11
9
  // required currently for client-side fetching as the pre-rendered page
12
10
  // loads with the previous tabs data on the first render after switching tabs. (or similar)
13
11
  if (entityListType !== exploreState.tabValue)
14
12
  return null;
15
- return (React.createElement(TableCreator, { columns: columnsConfig, getRowId: getRowId, items: listItems ?? [], listView: listView, loading: loading }));
13
+ return React.createElement(TableCreator, { listView: listView, loading: loading, table: table });
16
14
  };
@@ -1,3 +1,6 @@
1
- export interface EntityListProps {
1
+ import { RowData, Table } from "@tanstack/react-table";
2
+ export interface EntityListProps<T extends RowData> {
2
3
  entityListType: string;
4
+ loading: boolean;
5
+ table: Table<T>;
3
6
  }
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { Summaries } from "../summaries";
3
+ declare const meta: Meta<typeof Summaries>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
@@ -1,13 +1,9 @@
1
- import { Summaries } from "./summaries";
1
+ import { Summaries } from "../summaries";
2
2
  const meta = {
3
- argTypes: {
4
- summaries: { control: "object" },
5
- },
6
3
  component: Summaries,
7
- title: "Components/Summary",
8
4
  };
9
5
  export default meta;
10
- export const SummariesStory = {
6
+ export const Default = {
11
7
  args: {
12
8
  summaries: [
13
9
  { count: "1", label: "Species" },
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { Hero } from "../hero";
3
+ declare const meta: Meta<typeof Hero>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
@@ -0,0 +1,16 @@
1
+ import { composeStories } from "@storybook/react";
2
+ import React from "react";
3
+ import * as summaryStories from "../components/Summaries/stories/summaries.stories";
4
+ import { Hero } from "../hero";
5
+ const { Default: Summaries } = composeStories(summaryStories);
6
+ const meta = {
7
+ component: Hero,
8
+ parameters: { layout: "fullscreen" },
9
+ };
10
+ export default meta;
11
+ export const Default = {
12
+ args: {
13
+ Summaries: React.createElement(Summaries, null),
14
+ title: "Data Explorer",
15
+ },
16
+ };
@@ -1,2 +1,2 @@
1
1
  import { IndexProps } from "./types";
2
- export declare const Index: ({ chart, className, list, ListHero, SideBarButton, SubTitleHero, Summaries, Tabs, title, }: IndexProps) => JSX.Element;
2
+ export declare const Index: ({ categoryFilters, className, entityListType, entityName, ListHero, loading, SideBarButton, SubTitleHero, Summaries, Tabs, title, }: IndexProps) => JSX.Element;
@@ -1,17 +1,21 @@
1
1
  import React from "react";
2
2
  import { useLayoutDimensions } from "../../providers/layoutDimensions/hook";
3
+ import { ChartView } from "./components/EntitiesView/components/ChartView/chartView";
4
+ import { EntityList } from "./components/EntitiesView/components/EntityList/entityList";
3
5
  import { EntitiesView } from "./components/EntitiesView/entitiesView";
4
6
  import { useEntitiesView } from "./components/EntitiesView/hooks/UseEntitiesView/hook";
5
7
  import { VIEW_MODE } from "./components/EntitiesView/hooks/UseEntitiesView/types";
6
8
  import { Hero } from "./components/Hero/hero";
7
9
  import { Index as IndexLayout } from "./index.styles";
8
- export const Index = ({ chart, className, list, ListHero, SideBarButton, SubTitleHero, Summaries, Tabs, title, }) => {
10
+ import { useTable } from "./table/hook";
11
+ export const Index = ({ categoryFilters, className, entityListType, entityName, ListHero, loading, SideBarButton, SubTitleHero, Summaries, Tabs, title, }) => {
9
12
  const { onChange, viewMode, viewStatus } = useEntitiesView();
10
13
  const { dimensions } = useLayoutDimensions();
14
+ const { table } = useTable();
11
15
  return (React.createElement(IndexLayout, { className: className, marginTop: dimensions.header.height },
12
16
  React.createElement(Hero, { SideBarButton: SideBarButton, Summaries: Summaries, title: title }),
13
17
  SubTitleHero,
14
18
  Tabs,
15
19
  ListHero,
16
- React.createElement(EntitiesView, { onChange: onChange, viewMode: viewMode, viewStatus: viewStatus }, viewMode === VIEW_MODE.TABLE ? list : chart)));
20
+ React.createElement(EntitiesView, { onChange: onChange, viewMode: viewMode, viewStatus: viewStatus }, viewMode === VIEW_MODE.TABLE ? (React.createElement(EntityList, { entityListType: entityListType, loading: loading, table: table })) : (React.createElement(ChartView, { categoryFilters: categoryFilters, entityName: entityName, loading: loading })))));
17
21
  };
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { Index } from "../index";
3
+ declare const meta: Meta<typeof Index>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
@@ -0,0 +1,17 @@
1
+ import { composeStories } from "@storybook/react";
2
+ import React from "react";
3
+ import * as summaryStories from "../components/Hero/components/Summaries/stories/summaries.stories";
4
+ import { Index } from "../index";
5
+ const { Default: Summaries } = composeStories(summaryStories);
6
+ const meta = {
7
+ component: Index,
8
+ parameters: { layout: "fullscreen" },
9
+ };
10
+ export default meta;
11
+ export const Default = {
12
+ args: {
13
+ Summaries: React.createElement(Summaries, null),
14
+ Tabs: undefined,
15
+ title: "Explore Data",
16
+ },
17
+ };
@@ -0,0 +1,9 @@
1
+ import { CellContext, RowData } from "@tanstack/react-table";
2
+ import { ColumnConfig } from "../../../../../config/entities";
3
+ /**
4
+ * Creates a cell renderer function for a given column configuration.
5
+ * This factory pattern allows us to create cell renderers without defining JSX in hook files.
6
+ * @param config - The column configuration.
7
+ * @returns A function that renders the cell content.
8
+ */
9
+ export declare function createCell<T extends RowData>(config: ColumnConfig<T>): (context: CellContext<T, unknown>) => JSX.Element;
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { ComponentCreator } from "../../../../../components/ComponentCreator/ComponentCreator";
3
+ /**
4
+ * Creates a cell renderer function for a given column configuration.
5
+ * This factory pattern allows us to create cell renderers without defining JSX in hook files.
6
+ * @param config - The column configuration.
7
+ * @returns A function that renders the cell content.
8
+ */
9
+ export function createCell(config) {
10
+ return function CellCreator(context) {
11
+ return (React.createElement(ComponentCreator, { components: [config.componentConfig], response: context.row.original, viewContext: { cellContext: context } }));
12
+ };
13
+ }
@@ -0,0 +1,3 @@
1
+ import { RowData } from "@tanstack/react-table";
2
+ import { UseTable } from "./types";
3
+ export declare const useTable: <T extends RowData>() => UseTable<T>;
@@ -0,0 +1,166 @@
1
+ import { getCoreRowModel, getFacetedRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table";
2
+ import { useCallback, useEffect, useMemo } from "react";
3
+ import { track } from "../../../common/analytics/analytics";
4
+ import { EVENT_NAME, EVENT_PARAM, SORT_DIRECTION, } from "../../../common/analytics/entities";
5
+ import { useConfig } from "../../../hooks/useConfig";
6
+ import { useExploreMode } from "../../../hooks/useExploreMode/useExploreMode";
7
+ import { useExploreState } from "../../../hooks/useExploreState";
8
+ import { ExploreActionKind } from "../../../providers/exploreState";
9
+ import { DEFAULT_PAGINATION_STATE } from "../../../providers/exploreState/initializer/constants";
10
+ import { arrIncludesSome } from "../../Table/columnDef/columnFilters/filterFn";
11
+ import { COLUMN_DEF } from "../../Table/common/columnDef";
12
+ import { buildCategoryViews, getFacetedUniqueValuesWithArrayValues, getTableStatePagination, isClientFilteringEnabled, sortingFn, } from "../../Table/common/utils";
13
+ import { ROW_POSITION } from "../../Table/features/RowPosition/constants";
14
+ import { ROW_PREVIEW } from "../../Table/features/RowPreview/constants";
15
+ import { buildBaseColumnDef } from "../../TableCreator/common/utils";
16
+ import { useTableOptions } from "../../TableCreator/options/hook";
17
+ import { createCell } from "./coreOptions/columns/cellFactory";
18
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- TODO fix component length / complexity
19
+ export const useTable = () => {
20
+ const { entityConfig } = useConfig();
21
+ const exploreMode = useExploreMode();
22
+ const { exploreDispatch, exploreState } = useExploreState();
23
+ const tableOptions = useTableOptions();
24
+ const { entityPageState, filterState, listItems, paginationState, rowPreview, tabValue, } = exploreState;
25
+ const { getId: getRowId, list } = entityConfig;
26
+ const { columns: columnsConfig } = list;
27
+ const { columnVisibility, grouping, rowSelection, sorting } = entityPageState[tabValue];
28
+ const { currentPage, pageSize, rows: pageCount } = paginationState;
29
+ const clientFiltering = isClientFilteringEnabled(exploreMode);
30
+ const pagination = useMemo(() => getTableStatePagination(currentPage - 1, pageSize), [currentPage, pageSize]);
31
+ const columnDefs = useMemo(() => columnsConfig.reduce((acc, {
32
+ /**
33
+ * Applies the custom `arrIncludesSome` filter function as the default for multi-value filtering.
34
+ * Although `ColumnFilter["value"]` is typed as `unknown`, in practice it's consistently an array (`unknown[]`) in entity lists.
35
+ * This custom filter function supports multi-select filtering, even when individual cell values are single strings.
36
+ * This override of TanStack's default `arrIncludesSome` resolves a limitation where the base implementation
37
+ * does not support matching an array of filter values against a single string cell value.
38
+ * For range filtering, specify TanStack's `inNumberRange` filter function on the column definition.
39
+ */
40
+ filterFn = "arrIncludesSome", ...columnConfig }) => {
41
+ acc.push({
42
+ ...buildBaseColumnDef(columnConfig),
43
+ cell: createCell(columnConfig),
44
+ filterFn,
45
+ sortingFn,
46
+ });
47
+ return acc;
48
+ }, [
49
+ /* Initialize column definitions with the "row position" column */
50
+ COLUMN_DEF.ROW_POSITION,
51
+ /* Initialize column definitions with the "row selection" column */
52
+ COLUMN_DEF.ROW_SELECTION,
53
+ ]), [columnsConfig]);
54
+ const onSortingChange = (updater) => {
55
+ // TODO(cc) memoize `onSortingChange` with `useCallback`.
56
+ // TODO(cc) copy `onSortingChange` to ../options/sorting/hook.ts see src/components/Table/options/grouping/hook.ts for example.
57
+ exploreDispatch({
58
+ payload: typeof updater === "function" ? updater(sorting) : updater,
59
+ type: ExploreActionKind.UpdateSorting,
60
+ });
61
+ // Execute GTM tracking.
62
+ // TODO(cc) update tracking to handle sorting of multiple columns.
63
+ // TODO(cc) GTM tracking when `onSortingChange` is triggered only tracks the first column sorted, and takes the value from explore state which is not updated yet.
64
+ track(EVENT_NAME.ENTITY_TABLE_SORTED, {
65
+ [EVENT_PARAM.ENTITY_NAME]: exploreState.tabValue,
66
+ [EVENT_PARAM.COLUMN_NAME]: sorting?.[0]?.id, // TODO(cc) sorting should always be at least `[]` and never `undefined`.
67
+ [EVENT_PARAM.SORT_DIRECTION]: sorting?.[0]?.desc // TODO(cc) sorting should always be at least `[]` and never `undefined`.
68
+ ? SORT_DIRECTION.DESC
69
+ : SORT_DIRECTION.ASC,
70
+ });
71
+ };
72
+ const onRowPreviewChange = useCallback((updater) => {
73
+ exploreDispatch({
74
+ payload: typeof updater === "function" ? updater(rowPreview) : updater,
75
+ type: ExploreActionKind.UpdateRowPreview,
76
+ });
77
+ }, [exploreDispatch, rowPreview]);
78
+ const onRowSelectionChange = useCallback((updater) => {
79
+ // TODO(cc) refactor `onRowSelectionChange` to /options/rowSelection/hook.ts see onGroupingChange.
80
+ exploreDispatch({
81
+ payload: typeof updater === "function" ? updater(rowSelection) : updater,
82
+ type: ExploreActionKind.UpdateRowSelection,
83
+ });
84
+ }, [exploreDispatch, rowSelection]);
85
+ const state = {
86
+ columnVisibility,
87
+ grouping,
88
+ pagination,
89
+ rowPreview,
90
+ rowSelection,
91
+ sorting,
92
+ };
93
+ /**
94
+ * TODO: Update `ColumnConfig` to follow the `ColumnDef` API of TanStack Table.
95
+ * - Standardize column definitions to leverage the full power of TanStack Table's feature set and improve compatibility.
96
+ * TODO: Define `sorting` directly within `ListConfig` via the `tableOptions.initialState` property.
97
+ * - This will simplify the configuration structure and centralize table state definitions, reducing redundancy and improving clarity.
98
+ */
99
+ const table = useReactTable({
100
+ _features: [ROW_POSITION, ROW_PREVIEW],
101
+ columns: columnDefs,
102
+ data: listItems || [],
103
+ enableColumnFilters: true, // client-side filtering.
104
+ enableFilters: true, // client-side filtering.
105
+ enableMultiSort: clientFiltering, // TODO(cc) move to sorting options; default to false and let the table options in config flag this value.
106
+ filterFns: { arrIncludesSome },
107
+ getCoreRowModel: getCoreRowModel(),
108
+ getFacetedRowModel: clientFiltering ? getFacetedRowModel() : undefined,
109
+ getFacetedUniqueValues: clientFiltering
110
+ ? getFacetedUniqueValuesWithArrayValues()
111
+ : undefined,
112
+ getFilteredRowModel: clientFiltering ? getFilteredRowModel() : undefined,
113
+ getPaginationRowModel: getPaginationRowModel(),
114
+ getRowId,
115
+ getSortedRowModel: clientFiltering ? getSortedRowModel() : undefined,
116
+ manualPagination: true,
117
+ manualSorting: !clientFiltering,
118
+ onRowPreviewChange,
119
+ onRowSelectionChange,
120
+ onSortingChange,
121
+ pageCount,
122
+ state,
123
+ ...tableOptions,
124
+ });
125
+ const { getAllColumns, getRowModel, getState } = table;
126
+ const allColumns = getAllColumns();
127
+ const { columnFilters } = getState();
128
+ const { rows } = getRowModel();
129
+ // Sets react table column filters `columnFilters` state - for client-side filtering only - with update of filterState.
130
+ useEffect(() => {
131
+ if (clientFiltering) {
132
+ table.setColumnFilters(filterState.map(({ categoryKey, value }) => ({
133
+ id: categoryKey,
134
+ value,
135
+ })));
136
+ }
137
+ }, [clientFiltering, filterState, table]);
138
+ // Process explore response - client-side filtering only.
139
+ useEffect(() => {
140
+ if (!listItems || listItems.length === 0)
141
+ return;
142
+ if (clientFiltering) {
143
+ exploreDispatch({
144
+ payload: {
145
+ listItems,
146
+ loading: false,
147
+ paginationResponse: {
148
+ ...DEFAULT_PAGINATION_STATE,
149
+ pageSize: rows.filter(({ getIsGrouped }) => !getIsGrouped()).length,
150
+ rows: rows.filter(({ getIsGrouped }) => !getIsGrouped()).length,
151
+ },
152
+ selectCategories: buildCategoryViews(allColumns, columnFilters),
153
+ },
154
+ type: ExploreActionKind.ProcessExploreResponse,
155
+ });
156
+ }
157
+ }, [
158
+ allColumns,
159
+ clientFiltering,
160
+ columnFilters,
161
+ exploreDispatch,
162
+ listItems,
163
+ rows,
164
+ ]);
165
+ return { table };
166
+ };
@@ -0,0 +1,4 @@
1
+ import { RowData, Table } from "@tanstack/react-table";
2
+ export interface UseTable<T extends RowData> {
3
+ table: Table<T>;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,13 @@
1
1
  import { BaseComponentProps } from "components/types";
2
+ import { EntityConfig } from "config/entities";
2
3
  import { ReactNode } from "react";
4
+ import { CategoryFilter } from "../../components/Filter/components/Filters/filters";
3
5
  export interface IndexProps extends BaseComponentProps {
4
- chart?: ReactNode;
5
- list?: ReactNode;
6
+ categoryFilters: CategoryFilter[];
7
+ entityListType: string;
8
+ entityName: EntityConfig["label"];
6
9
  ListHero?: ReactNode | ReactNode[];
10
+ loading: boolean;
7
11
  SideBarButton?: ReactNode;
8
12
  SubTitleHero?: ReactNode | ReactNode[];
9
13
  Summaries?: ReactNode;
@@ -1,24 +1,9 @@
1
- import { ColumnDef, CoreOptions, RowData } from "@tanstack/react-table";
2
- import { ListConfig, ListViewConfig } from "../../config/entities";
1
+ import { RowData, Table as TanStackTable } from "@tanstack/react-table";
2
+ import { ListViewConfig } from "../../config/entities";
3
3
  export interface TableProps<T extends RowData> {
4
- columns: ColumnDef<T>[];
5
- getRowId?: CoreOptions<T>["getRowId"];
6
- items: T[];
7
4
  listView?: ListViewConfig;
8
- loading?: boolean;
9
- tableOptions?: ListConfig<T>["tableOptions"];
5
+ loading: boolean;
6
+ table: TanStackTable<T>;
10
7
  }
11
- /**
12
- * This table can be Controlled or Uncontrolled based on the set of props passed to it.
13
- * Controlled table will receive the navigation functions, and it will be used for dynamic loads.
14
- * Uncontrolled table will take advantage of React Table's state and will be used for static loads.
15
- * @param tableProps - Set of props required for displaying the table.
16
- * @param tableProps.columns - Set of columns to display.
17
- * @param tableProps.getRowId - Function to customize the row ID.
18
- * @param tableProps.items - Row data to display.
19
- * @param tableProps.listView - List view configuration.
20
- * @param tableProps.tableOptions - TanStack table options.
21
- * @returns Configured table element for display.
22
- */
23
- export declare const TableComponent: <T extends RowData>({ columns, getRowId, items, listView, tableOptions, }: TableProps<T>) => JSX.Element;
8
+ export declare const TableComponent: <T extends RowData>({ listView, loading, table, }: TableProps<T>) => JSX.Element;
24
9
  export declare const Table: typeof TableComponent;