239 lines
6.6 KiB
TypeScript

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 StatusChangeHandler = (rowId: number, newStatus: string) => void;
const selectEnableStatusActions = (
_state: RootState,
enableStatusActions: boolean
) => enableStatusActions;
const selectExtraColumns = (
_state: RootState,
_enableStatusActions: boolean,
extraColumns?: string[] | null
) => extraColumns ?? null;
const selectShowExtraColumns = (
_state: RootState,
_enableStatusActions: boolean,
_extraColumns?: string[] | null,
showExtraColumns = false
) => showExtraColumns;
const selectLocalRows = (
_state: RootState,
_enableStatusActions: boolean,
_extraColumns?: string[] | null,
_showExtraColumns?: boolean,
localRows?: DataRowBase[]
) => localRows ?? [];
const noopStatusChangeHandler: StatusChangeHandler = () => {};
const selectStatusChangeHandler = (
_state: RootState,
_enableStatusActions: boolean,
_extraColumns?: string[] | null,
_showExtraColumns?: boolean,
_localRows?: DataRowBase[],
handleStatusChange?: StatusChangeHandler
) => handleStatusChange ?? noopStatusChangeHandler;
export const selectBaseColumns = createSelector(
[selectEnableStatusActions],
enableStatusActions => {
if (!enableStatusActions) {
return TABLE_COLUMNS;
}
return [
...TABLE_COLUMNS,
{
field: "actions",
headerName: "Actions",
width: 160,
sortable: false,
filterable: false,
} as GridColDef,
];
}
);
export const selectVisibleColumns = createSelector(
[selectBaseColumns, selectExtraColumns, selectShowExtraColumns],
(baseColumns, extraColumns, showExtraColumns) => {
if (!extraColumns || extraColumns.length === 0) {
return baseColumns;
}
return showExtraColumns
? baseColumns
: baseColumns.filter(col => !extraColumns.includes(col.field));
}
);
export const selectResolvedTransactionStatuses = createSelector(
[selectTransactionStatuses],
statuses => (statuses.length > 0 ? statuses : TRANSACTION_STATUS_FALLBACK)
);
export const selectEnhancedColumns = createSelector(
[
selectVisibleColumns,
selectLocalRows,
selectStatusChangeHandler,
selectResolvedTransactionStatuses,
],
(
visibleColumns,
localRows,
handleStatusChange,
resolvedStatusOptions
): GridColDef[] => {
return visibleColumns.map(col => {
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 (
<Box
sx={{
backgroundColor: bgColor,
color: textColor,
px: 1.5,
py: 0.5,
borderRadius: 1,
fontWeight: 500,
textTransform: "capitalize",
display: "inline-block",
width: "100%",
textAlign: "center",
}}
>
{params.value}
</Box>
);
},
};
}
if (col.field === "userId") {
return {
...col,
headerAlign: "center",
align: "center",
renderCell: (params: GridRenderCellParams) => (
<Box
sx={{
display: "grid",
gridTemplateColumns: "1fr auto",
alignItems: "center",
width: "100%",
px: 1,
}}
onClick={e => e.stopPropagation()}
>
<Box
sx={{
fontWeight: 500,
fontSize: "0.875rem",
color: "text.primary",
}}
>
{params.value}
</Box>
<IconButton
href={`/users/${params.value}`}
target="_blank"
rel="noopener noreferrer"
size="small"
sx={{ p: 0.5, ml: 1 }}
onClick={e => e.stopPropagation()}
>
<OpenInNewIcon fontSize="small" />
</IconButton>
</Box>
),
};
}
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: string[] = Array.from(new Set(options));
return (
<Select<string>
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 => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
);
},
};
}
return col;
}) as GridColDef[];
}
);