From d9485bcf2e972f58784f50093ef4687066405a78 Mon Sep 17 00:00:00 2001 From: Mitchell Magro Date: Wed, 19 Nov 2025 20:42:31 +0100 Subject: [PATCH] Added more to transaction table perfomrance --- app/features/DataTable/re-selectors/index.tsx | 112 +++++++++++++++--- app/features/DataTable/types.ts | 18 +++ app/redux/metadata/selectors.ts | 5 + 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/app/features/DataTable/re-selectors/index.tsx b/app/features/DataTable/re-selectors/index.tsx index d0386bb..3afe1dc 100644 --- a/app/features/DataTable/re-selectors/index.tsx +++ b/app/features/DataTable/re-selectors/index.tsx @@ -42,25 +42,103 @@ const propsLocalRows = (_: RootState, props: SelectorProps) => const propsStatusChangeHandler = (_: RootState, props: SelectorProps) => props.handleStatusChange; +// ------------------------- +// Helper: Format field name to header name +// ------------------------- + +/** + * Converts a field name to a readable header name + * e.g., "userId" -> "User ID", "transactionId" -> "Transaction ID" + */ +const formatFieldNameToHeader = (fieldName: string): string => { + // Handle camelCase: insert space before capital letters and capitalize first letter + return fieldName + .replace(/([A-Z])/g, " $1") // Add space before capital letters + .replace(/^./, str => str.toUpperCase()) // Capitalize first letter + .trim(); +}; + +// ------------------------- +// Dynamic Columns from Row Data +// ------------------------- + +const makeSelectDynamicColumns = () => + createSelector([propsLocalRows], (localRows): GridColDef[] => { + // If no rows, fall back to static columns + if (!localRows || localRows.length === 0) { + return TABLE_COLUMNS; + } + + // Get all unique field names from the row data + const fieldSet = new Set(); + localRows.forEach(row => { + Object.keys(row).forEach(key => { + if (key !== "options") { + // Exclude internal fields + fieldSet.add(key); + } + }); + }); + + // Build columns from actual row data fields + const dynamicColumns: GridColDef[] = Array.from(fieldSet).map(field => { + // Format field name to readable header + const headerName = formatFieldNameToHeader(field); + + // Set default widths based on field type + let width = 150; + if (field.includes("id") || field.includes("Id")) { + width = 180; + } else if (field === "amount" || field === "currency") { + width = 120; + } else if (field === "status") { + width = 120; + } else if ( + field.includes("date") || + field.includes("Date") || + field === "dateTime" || + field === "created" || + field === "modified" + ) { + width = 180; + } + + return { + field, + headerName, + width, + sortable: true, + filterable: true, + } as GridColDef; + }); + + return dynamicColumns; + }); + // ------------------------- // Base Columns // ------------------------- const makeSelectBaseColumns = () => - createSelector([propsEnableStatusActions], enableStatusActions => { - if (!enableStatusActions) return TABLE_COLUMNS; + createSelector( + [makeSelectDynamicColumns(), propsEnableStatusActions], + (dynamicColumns, enableStatusActions) => { + const baseColumns = dynamicColumns; - return [ - ...TABLE_COLUMNS, - { - field: "actions", - headerName: "Actions", - width: 160, - sortable: false, - filterable: false, - } as GridColDef, - ]; - }); + if (!enableStatusActions) return baseColumns; + + return [ + ...baseColumns, + { + field: "actions", + headerName: "Actions", + width: 160, + sortable: false, + filterable: false, + } as GridColDef, + ]; + } + ); // ------------------------- // Visible Columns @@ -70,14 +148,17 @@ const makeSelectVisibleColumns = () => createSelector( [makeSelectBaseColumns(), propsExtraColumns, propsShowExtraColumns], (baseColumns, extraColumns, showExtraColumns) => { + // Columns are already built from row data, so they're all valid if (!extraColumns || extraColumns.length === 0) return baseColumns; - return showExtraColumns + const visibleColumns = showExtraColumns ? baseColumns : baseColumns.filter(col => !extraColumns.includes(col.field)); + + console.log("visibleColumns", visibleColumns); + return visibleColumns; } ); - // ------------------------- // Resolved Statuses (STATE-based) // ------------------------- @@ -105,6 +186,7 @@ export const makeSelectEnhancedColumns = () => handleStatusChange, resolvedStatusOptions ): GridColDef[] => { + console.log("visibleColumns", visibleColumns); return visibleColumns.map(col => { // -------------------------------- // 1. STATUS COLUMN RENDERER diff --git a/app/features/DataTable/types.ts b/app/features/DataTable/types.ts index 5cc2efe..b332c06 100644 --- a/app/features/DataTable/types.ts +++ b/app/features/DataTable/types.ts @@ -17,3 +17,21 @@ export interface DataRowBase { status?: string; options?: { value: string; label: string }[]; } + +export interface ITransactions { + id: string; + psp_id: string; + method_id: string; + merchant_id: string; + external_id?: string; // optional: may not always be present + customer?: string; // keep as string unless you provide structure + type?: string; + currency?: string; + amount: number | string; // sometimes APIs return strings for money + status?: string; + notes?: string; + creator?: string; + created: string; // ISO datetime string from API + modifier?: string; + modified?: string; // ISO datetime string or undefined +} diff --git a/app/redux/metadata/selectors.ts b/app/redux/metadata/selectors.ts index 6d84b41..89ae029 100644 --- a/app/redux/metadata/selectors.ts +++ b/app/redux/metadata/selectors.ts @@ -38,3 +38,8 @@ export const selectConditionOperators = ( state: RootState ): Record | undefined => state.metadata.data?.field_names?.conditions; + +export const selectTransactionFieldNames = ( + state: RootState +): Record | undefined => + state.metadata.data?.field_names?.transactions;