import { createSelector } from "@reduxjs/toolkit"; import { Box, IconButton, MenuItem, Select } from "@mui/material"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import { GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; import { RootState } from "@/app/redux/store"; import { TABLE_COLUMNS } from "../constants"; import { selectTransactionStatuses } from "@/app/redux/metadata/selectors"; import { DataRowBase } from "../types"; const TRANSACTION_STATUS_FALLBACK: string[] = [ "pending", "completed", "failed", "inprogress", "error", ]; type SelectorProps = { enableStatusActions: boolean; extraColumns?: string[] | null; showExtraColumns?: boolean; localRows: DataRowBase[]; handleStatusChange: (rowId: number, newStatus: string) => void; }; // ------------------------- // Basic Selectors (props-driven) // ------------------------- const propsEnableStatusActions = (_: RootState, props: SelectorProps) => props.enableStatusActions; const propsExtraColumns = (_: RootState, props: SelectorProps) => props.extraColumns ?? null; const propsShowExtraColumns = (_: RootState, props: SelectorProps) => props.showExtraColumns ?? false; const propsLocalRows = (_: RootState, props: SelectorProps) => props.localRows ?? []; 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( [makeSelectDynamicColumns(), propsEnableStatusActions], (dynamicColumns, enableStatusActions) => { const baseColumns = dynamicColumns; if (!enableStatusActions) return baseColumns; return [ ...baseColumns, { field: "actions", headerName: "Actions", width: 160, sortable: false, filterable: false, } as GridColDef, ]; } ); // ------------------------- // Visible Columns // ------------------------- 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; const visibleColumns = showExtraColumns ? baseColumns : baseColumns.filter(col => !extraColumns.includes(col.field)); console.log("visibleColumns", visibleColumns); return visibleColumns; } ); // ------------------------- // Resolved Statuses (STATE-based) // ------------------------- const makeSelectResolvedStatuses = () => createSelector([selectTransactionStatuses], statuses => statuses.length > 0 ? statuses : TRANSACTION_STATUS_FALLBACK ); // ------------------------- // Enhanced Columns // ------------------------- export const makeSelectEnhancedColumns = () => createSelector( [ makeSelectVisibleColumns(), propsLocalRows, propsStatusChangeHandler, makeSelectResolvedStatuses(), ], ( visibleColumns, localRows, handleStatusChange, resolvedStatusOptions ): GridColDef[] => { console.log("visibleColumns", visibleColumns); return visibleColumns.map(col => { // -------------------------------- // 1. STATUS COLUMN RENDERER // -------------------------------- if (col.field === "status") { return { ...col, renderCell: (params: GridRenderCellParams) => { const value = params.value?.toLowerCase(); let bgColor = "#e0e0e0"; let textColor = "#000"; switch (value) { case "completed": bgColor = "#d0f0c0"; textColor = "#1b5e20"; break; case "pending": bgColor = "#fff4cc"; textColor = "#9e7700"; break; case "inprogress": bgColor = "#cce5ff"; textColor = "#004085"; break; case "error": bgColor = "#ffcdd2"; textColor = "#c62828"; break; } return ( {params.value} ); }, }; } // -------------------------------- // 2. USER ID COLUMN // -------------------------------- if (col.field === "userId") { return { ...col, headerAlign: "center", align: "center", renderCell: (params: GridRenderCellParams) => ( e.stopPropagation()} > {params.value} e.stopPropagation()} > ), }; } // -------------------------------- // 3. ACTIONS COLUMN // -------------------------------- if (col.field === "actions") { return { ...col, renderCell: (params: GridRenderCellParams) => { const currentRow = localRows.find(row => row.id === params.id); const options = currentRow?.options?.map(option => option.value) ?? resolvedStatusOptions; const uniqueOptions = Array.from(new Set(options)); return ( value={currentRow?.status ?? ""} onChange={e => handleStatusChange( params.id as number, e.target.value as string ) } size="small" fullWidth displayEmpty sx={{ "& .MuiOutlinedInput-notchedOutline": { border: "none" }, "& .MuiSelect-select": { py: 0.5 }, }} onClick={e => e.stopPropagation()} > {uniqueOptions.map(option => ( {option} ))} ); }, }; } return col; }); } );