Refactored more on data tables

This commit is contained in:
Mitchell Magro 2025-11-19 16:30:16 +01:00
parent 2b6df3899b
commit 4f230fa208
12 changed files with 379 additions and 259 deletions

View File

@ -32,10 +32,32 @@ export async function POST(request: NextRequest) {
queryParts.push(`sort=${sort.field}:${sort.order}`); queryParts.push(`sort=${sort.field}:${sort.order}`);
} }
// Track date ranges separately so we can emit BETWEEN/>/< syntax
const dateRanges: Record<string, { start?: string; end?: string }> = {};
// Process filters - convert FilterValue objects to operator/value format // Process filters - convert FilterValue objects to operator/value format
for (const [key, filterValue] of Object.entries(filters)) { for (const [key, filterValue] of Object.entries(filters)) {
if (!filterValue) continue; if (!filterValue) continue;
// Handle date range helpers (e.g. Created_start / Created_end)
if (/_start$|_end$/.test(key)) {
const baseField = key.replace(/_(start|end)$/, "");
if (!dateRanges[baseField]) {
dateRanges[baseField] = {};
}
const targetKey = key.endsWith("_start") ? "start" : "end";
const stringValue =
typeof filterValue === "string"
? filterValue
: (filterValue as { value?: string }).value;
if (stringValue) {
dateRanges[baseField][targetKey] = stringValue;
}
continue;
}
let op: string; let op: string;
let value: string; let value: string;
@ -57,6 +79,24 @@ export async function POST(request: NextRequest) {
queryParts.push(`${key}=${op}/${encodedValue}`); queryParts.push(`${key}=${op}/${encodedValue}`);
} }
// Emit date range filters using backend format
for (const [field, { start, end }] of Object.entries(dateRanges)) {
if (start && end) {
queryParts.push(
`${field}=BETWEEN/${encodeURIComponent(start)}/${encodeURIComponent(
end
)}`
);
continue;
}
if (start) {
queryParts.push(`${field}=>/${encodeURIComponent(start)}`);
} else if (end) {
queryParts.push(`${field}=</${encodeURIComponent(end)}`);
}
}
const queryString = queryParts.join("&"); const queryString = queryParts.join("&");
const backendUrl = `${BE_BASE_URL}/api/v1/transactions${queryString ? `?${queryString}` : ""}`; const backendUrl = `${BE_BASE_URL}/api/v1/transactions${queryString ? `?${queryString}` : ""}`;

View File

@ -9,6 +9,8 @@ import {
} from "@mui/x-data-grid"; } from "@mui/x-data-grid";
import { getAudits } from "@/app/services/audits"; import { getAudits } from "@/app/services/audits";
import "./page.scss"; import "./page.scss";
import TextField from "@mui/material/TextField";
import { Box, debounce } from "@mui/material";
type AuditRow = Record<string, unknown> & { id: string | number }; type AuditRow = Record<string, unknown> & { id: string | number };
@ -178,6 +180,8 @@ export default function AuditPage() {
pageSize: DEFAULT_PAGE_SIZE, pageSize: DEFAULT_PAGE_SIZE,
}); });
const [sortModel, setSortModel] = useState<GridSortModel>([]); const [sortModel, setSortModel] = useState<GridSortModel>([]);
const [entitySearch, setEntitySearch] = useState<string>("");
const [entitySearchInput, setEntitySearchInput] = useState<string>("");
useEffect(() => { useEffect(() => {
const controller = new AbortController(); const controller = new AbortController();
@ -191,11 +195,16 @@ export default function AuditPage() {
? `${sortModel[0].field}:${sortModel[0].sort}` ? `${sortModel[0].field}:${sortModel[0].sort}`
: undefined; : undefined;
const entityParam = entitySearch.trim()
? `LIKE/${entitySearch.trim()}`
: undefined;
try { try {
const payload = (await getAudits({ const payload = (await getAudits({
limit: paginationModel.pageSize, limit: paginationModel.pageSize,
page: paginationModel.page + 1, page: paginationModel.page + 1,
sort: sortParam, sort: sortParam,
entity: entityParam,
signal: controller.signal, signal: controller.signal,
})) as AuditApiResponse; })) as AuditApiResponse;
@ -229,7 +238,7 @@ export default function AuditPage() {
fetchAudits(); fetchAudits();
return () => controller.abort(); return () => controller.abort();
}, [paginationModel, sortModel]); }, [paginationModel, sortModel, entitySearch]);
const handlePaginationChange = (model: GridPaginationModel) => { const handlePaginationChange = (model: GridPaginationModel) => {
setPaginationModel(model); setPaginationModel(model);
@ -240,6 +249,26 @@ export default function AuditPage() {
setPaginationModel(prev => ({ ...prev, page: 0 })); setPaginationModel(prev => ({ ...prev, page: 0 }));
}; };
const debouncedSetEntitySearch = useMemo(
() =>
debounce((value: string) => {
setEntitySearch(value);
setPaginationModel(prev => ({ ...prev, page: 0 }));
}, 500),
[]
);
useEffect(() => {
return () => {
debouncedSetEntitySearch.clear();
};
}, [debouncedSetEntitySearch]);
const handleEntitySearchChange = (value: string) => {
setEntitySearchInput(value);
debouncedSetEntitySearch(value);
};
const pageTitle = useMemo( const pageTitle = useMemo(
() => () =>
sortModel.length && sortModel[0].field sortModel.length && sortModel[0].field
@ -250,6 +279,16 @@ export default function AuditPage() {
return ( return (
<div className="audits-page"> <div className="audits-page">
<Box sx={{ display: "flex", gap: 2, mt: 5 }}>
<TextField
label="Search by Entity"
variant="outlined"
size="small"
value={entitySearchInput}
onChange={e => handleEntitySearchChange(e.target.value)}
sx={{ width: 300, backgroundColor: "#f0f0f0" }}
/>
</Box>
<h1 className="page-title">{pageTitle}</h1> <h1 className="page-title">{pageTitle}</h1>
{error && ( {error && (
<div className="error-alert" role="alert"> <div className="error-alert" role="alert">

View File

@ -18,11 +18,18 @@ export default function AllTransactionPage() {
const pagination = useSelector(selectPagination); const pagination = useSelector(selectPagination);
const sort = useSelector(selectSort); const sort = useSelector(selectSort);
const [tableRows, setTableRows] = useState<TransactionRow[]>([]); const [tableData, setTableData] = useState<{
transactions: TransactionRow[];
total: number;
}>({ transactions: [], total: 0 });
const [totalRows, setRowCount] = useState(0);
const extraColumns: string[] = []; // static for now const extraColumns: string[] = []; // static for now
// Memoize rows to avoid new reference each render // Memoize rows to avoid new reference each render
const memoizedRows = useMemo(() => tableRows, [tableRows]); const memoizedRows = useMemo(
() => tableData.transactions,
[tableData.transactions]
);
// Fetch data when filters, pagination, or sort changes // Fetch data when filters, pagination, or sort changes
useEffect(() => { useEffect(() => {
@ -37,12 +44,12 @@ export default function AllTransactionPage() {
if (!response.ok) { if (!response.ok) {
dispatch(setAdvancedSearchError("Failed to fetch transactions")); dispatch(setAdvancedSearchError("Failed to fetch transactions"));
setTableRows([]); setTableData({ transactions: [], total: 0 });
return; return;
} }
const backendData = await response.json(); const data = await response.json();
const transactions = backendData.transactions || []; const transactions = data.transactions || [];
const rows = transactions.map((tx: BackendTransaction) => ({ const rows = transactions.map((tx: BackendTransaction) => ({
id: tx.id || 0, id: tx.id || 0,
@ -59,19 +66,25 @@ export default function AllTransactionPage() {
modified: tx.modified, modified: tx.modified,
})); }));
setTableRows(rows); setTableData({ transactions: rows, total: data?.total });
} catch (error) { } catch (error) {
dispatch( dispatch(
setAdvancedSearchError( setAdvancedSearchError(
error instanceof Error ? error.message : "Unknown error" error instanceof Error ? error.message : "Unknown error"
) )
); );
setTableRows([]); setTableData({ transactions: [], total: 0 });
} }
}; };
fetchData(); fetchData();
}, [dispatch, filters, pagination, sort]); }, [dispatch, filters, pagination, sort]);
return <DataTable rows={memoizedRows} extraColumns={extraColumns} />; return (
<DataTable
rows={memoizedRows}
extraColumns={extraColumns}
totalRows={tableData.total}
/>
);
} }

View File

@ -21,6 +21,7 @@ export default function DepositTransactionPage() {
const pagination = useSelector(selectPagination); const pagination = useSelector(selectPagination);
const sort = useSelector(selectSort); const sort = useSelector(selectSort);
const [tableRows, setTableRows] = useState<TransactionRow[]>([]); const [tableRows, setTableRows] = useState<TransactionRow[]>([]);
const [rowCount, setRowCount] = useState(0);
const memoizedRows = useMemo(() => tableRows, [tableRows]); const memoizedRows = useMemo(() => tableRows, [tableRows]);
@ -75,6 +76,7 @@ export default function DepositTransactionPage() {
})); }));
setTableRows(rows); setTableRows(rows);
setRowCount(100);
dispatch(setStatus("succeeded")); dispatch(setStatus("succeeded"));
} catch (error) { } catch (error) {
dispatch( dispatch(
@ -89,5 +91,7 @@ export default function DepositTransactionPage() {
fetchDeposits(); fetchDeposits();
}, [dispatch, depositFilters, pagination, sort]); }, [dispatch, depositFilters, pagination, sort]);
return <DataTable rows={memoizedRows} enableStatusActions />; return (
<DataTable rows={memoizedRows} enableStatusActions totalRows={rowCount} />
);
} }

View File

@ -28,6 +28,7 @@ import {
} from "@/app/redux/advanedSearch/advancedSearchSlice"; } from "@/app/redux/advanedSearch/advancedSearchSlice";
import { selectFilters } from "@/app/redux/advanedSearch/selectors"; import { selectFilters } from "@/app/redux/advanedSearch/selectors";
import { normalizeValue, defaultOperatorForField } from "./utils/utils"; import { normalizeValue, defaultOperatorForField } from "./utils/utils";
import { selectConditionOperators } from "@/app/redux/metadata/selectors";
// ----------------------------------------------------- // -----------------------------------------------------
// COMPONENT // COMPONENT
@ -42,7 +43,9 @@ export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
// Local form state for UI (synced with Redux) // Local form state for UI (synced with Redux)
const [formValues, setFormValues] = useState<Record<string, string>>({}); const [formValues, setFormValues] = useState<Record<string, string>>({});
const [operators, setOperators] = useState<Record<string, string>>({}); const [operators, setOperators] = useState<Record<string, string>>({});
const conditionOperators = useSelector(selectConditionOperators);
console.log("[conditionOperators]", conditionOperators);
// ----------------------------------------------------- // -----------------------------------------------------
// SYNC REDUX FILTERS TO LOCAL STATE ON LOAD // SYNC REDUX FILTERS TO LOCAL STATE ON LOAD
// ----------------------------------------------------- // -----------------------------------------------------
@ -255,12 +258,14 @@ export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
updateOperator(field, e.target.value) updateOperator(field, e.target.value)
} }
> >
<MenuItem value=">=">Greater or equal</MenuItem> {Object.entries(conditionOperators ?? {}).map(
<MenuItem value="<=">Less or equal</MenuItem> ([key, value]) => (
<MenuItem value="=">Equal</MenuItem> <MenuItem key={key} value={value}>
<MenuItem value="!=">Not equal</MenuItem> {key.replace(/_/g, " ")}{" "}
<MenuItem value=">">Greater</MenuItem> {/* Optional: make it readable */}
<MenuItem value="<">Less</MenuItem> </MenuItem>
)
)}
</Select> </Select>
</FormControl> </FormControl>

View File

@ -1,17 +1,3 @@
// -----------------------------------------------------
// UTILITIES
// -----------------------------------------------------
export const extractOperator = (val?: string | null): string | null => {
if (!val) return null;
const match = val.match(/^(==|!=|>=|<=|LIKE|>|<)/);
return match ? match[0] : null;
};
export const formatWithOperator = (operator: string, value: string) =>
`${operator}/${value}`;
export const normalizeValue = (input: any): string => { export const normalizeValue = (input: any): string => {
if (input == null) return ""; if (input == null) return "";
if (typeof input === "string" || typeof input === "number") if (typeof input === "string" || typeof input === "number")
@ -27,22 +13,6 @@ export const normalizeValue = (input: any): string => {
return ""; return "";
}; };
export const encodeFilter = (fullValue: string): string => {
// Split ONLY on the first slash
const index = fullValue.indexOf("/");
if (index === -1) return fullValue;
const operator = fullValue.slice(0, index);
const rawValue = fullValue.slice(index + 1);
return `${operator}/${encodeURIComponent(rawValue)}`;
};
export const decodeFilter = (encoded: string): string => {
const [operator, encodedValue] = encoded.split("/");
return `${operator}/${decodeURIComponent(encodedValue)}`;
};
// Default operator based on field and type // Default operator based on field and type
export const defaultOperatorForField = ( export const defaultOperatorForField = (
field: string, field: string,

View File

@ -1,29 +1,38 @@
"use client"; "use client";
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useMemo } from "react";
import { DataGrid } from "@mui/x-data-grid"; import { DataGrid, GridPaginationModel } from "@mui/x-data-grid";
import { Box, Paper, Alert } from "@mui/material"; import { Box, Paper, Alert } from "@mui/material";
import DataTableHeader from "./DataTableHeader"; import DataTableHeader from "./DataTableHeader";
import StatusChangeDialog from "./StatusChangeDialog"; import StatusChangeDialog from "./StatusChangeDialog";
import Spinner from "@/app/components/Spinner/Spinner"; import Spinner from "@/app/components/Spinner/Spinner";
import { selectStatus, selectError } from "@/app/redux/advanedSearch/selectors"; import {
import { selectEnhancedColumns } from "./re-selectors"; selectStatus,
import { useSelector } from "react-redux"; selectError,
selectPagination,
selectPaginationModel,
} from "@/app/redux/advanedSearch/selectors";
import { makeSelectEnhancedColumns } from "./re-selectors";
import { useDispatch, useSelector } from "react-redux";
import { DataRowBase } from "./types"; import { DataRowBase } from "./types";
import { setPagination } from "@/app/redux/advanedSearch/advancedSearchSlice";
import { AppDispatch } from "@/app/redux/store";
interface DataTableProps<TRow extends DataRowBase> { interface DataTableProps<TRow extends DataRowBase> {
rows: TRow[]; rows: TRow[];
extraColumns?: string[]; extraColumns?: string[];
enableStatusActions?: boolean; enableStatusActions?: boolean;
totalRows?: number;
} }
const DataTable = <TRow extends DataRowBase>({ const DataTable = <TRow extends DataRowBase>({
rows, rows: localRows,
extraColumns, extraColumns,
enableStatusActions = false, enableStatusActions = false,
totalRows: totalRows,
}: DataTableProps<TRow>) => { }: DataTableProps<TRow>) => {
const dispatch = useDispatch<AppDispatch>();
const [showExtraColumns, setShowExtraColumns] = useState(false); const [showExtraColumns, setShowExtraColumns] = useState(false);
const [localRows, setLocalRows] = useState(rows);
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [selectedRowId, setSelectedRowId] = useState<number | null>(null); const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
const [pendingStatus, setPendingStatus] = useState<string>(""); const [pendingStatus, setPendingStatus] = useState<string>("");
@ -35,9 +44,21 @@ const DataTable = <TRow extends DataRowBase>({
const status = useSelector(selectStatus); const status = useSelector(selectStatus);
const errorMessage = useSelector(selectError); const errorMessage = useSelector(selectError);
useEffect(() => { const pagination = useSelector(selectPagination);
setLocalRows(rows); const paginationModel = useSelector(selectPaginationModel);
}, [rows]);
const handlePaginationModelChange = useCallback(
(model: GridPaginationModel) => {
console.log("model", model);
const nextPage = model.page + 1;
const nextLimit = model.pageSize;
if (nextPage !== pagination.page || nextLimit !== pagination.limit) {
dispatch(setPagination({ page: nextPage, limit: nextLimit }));
}
},
[dispatch, pagination.page, pagination.limit]
);
const handleStatusChange = useCallback((rowId: number, newStatus: string) => { const handleStatusChange = useCallback((rowId: number, newStatus: string) => {
setSelectedRowId(rowId); setSelectedRowId(rowId);
@ -78,11 +99,6 @@ const DataTable = <TRow extends DataRowBase>({
); );
} }
setLocalRows(prev =>
prev.map(row =>
row.id === selectedRowId ? { ...row, status: pendingStatus } : row
)
);
setModalOpen(false); setModalOpen(false);
setReason(""); setReason("");
setPendingStatus(""); setPendingStatus("");
@ -97,18 +113,17 @@ const DataTable = <TRow extends DataRowBase>({
} }
}; };
// Columns with custom renderers const selectEnhancedColumns = useMemo(makeSelectEnhancedColumns, []);
const enhancedColumns = useSelector(state => const enhancedColumns = useSelector(state =>
selectEnhancedColumns( selectEnhancedColumns(state, {
state,
enableStatusActions, enableStatusActions,
extraColumns, extraColumns,
showExtraColumns, showExtraColumns,
localRows, localRows,
handleStatusChange handleStatusChange,
) })
); );
return ( return (
<> <>
{status === "loading" && <Spinner size="small" color="#fff" />} {status === "loading" && <Spinner size="small" color="#fff" />}
@ -130,6 +145,10 @@ const DataTable = <TRow extends DataRowBase>({
<DataGrid <DataGrid
rows={localRows} rows={localRows}
columns={enhancedColumns} columns={enhancedColumns}
paginationModel={paginationModel}
onPaginationModelChange={handlePaginationModelChange}
paginationMode={totalRows ? "server" : "client"}
rowCount={totalRows}
pageSizeOptions={[10, 25, 50, 100]} pageSizeOptions={[10, 25, 50, 100]}
sx={{ sx={{
border: 0, border: 0,

View File

@ -33,5 +33,5 @@ export const TABLE_SEARCH_LABELS: ISearchLabel[] = [
options: ["pending", "completed", "failed"], options: ["pending", "completed", "failed"],
}, },
{ label: "Amount", field: "Amount", type: "text" }, { label: "Amount", field: "Amount", type: "text" },
{ label: "Date / Time", field: "created", type: "date" }, { label: "Date / Time", field: "Created", type: "date" },
]; ];

View File

@ -15,51 +15,40 @@ const TRANSACTION_STATUS_FALLBACK: string[] = [
"error", "error",
]; ];
type StatusChangeHandler = (rowId: number, newStatus: string) => void; type SelectorProps = {
enableStatusActions: boolean;
extraColumns?: string[] | null;
showExtraColumns?: boolean;
localRows: DataRowBase[];
handleStatusChange: (rowId: number, newStatus: string) => void;
};
const selectEnableStatusActions = ( // -------------------------
_state: RootState, // Basic Selectors (props-driven)
enableStatusActions: boolean // -------------------------
) => enableStatusActions;
const selectExtraColumns = ( const propsEnableStatusActions = (_: RootState, props: SelectorProps) =>
_state: RootState, props.enableStatusActions;
_enableStatusActions: boolean,
extraColumns?: string[] | null
) => extraColumns ?? null;
const selectShowExtraColumns = ( const propsExtraColumns = (_: RootState, props: SelectorProps) =>
_state: RootState, props.extraColumns ?? null;
_enableStatusActions: boolean,
_extraColumns?: string[] | null,
showExtraColumns = false
) => showExtraColumns;
const selectLocalRows = ( const propsShowExtraColumns = (_: RootState, props: SelectorProps) =>
_state: RootState, props.showExtraColumns ?? false;
_enableStatusActions: boolean,
_extraColumns?: string[] | null,
_showExtraColumns?: boolean,
localRows?: DataRowBase[]
) => localRows ?? [];
const noopStatusChangeHandler: StatusChangeHandler = () => {}; const propsLocalRows = (_: RootState, props: SelectorProps) =>
props.localRows ?? [];
const selectStatusChangeHandler = ( const propsStatusChangeHandler = (_: RootState, props: SelectorProps) =>
_state: RootState, props.handleStatusChange;
_enableStatusActions: boolean,
_extraColumns?: string[] | null,
_showExtraColumns?: boolean,
_localRows?: DataRowBase[],
handleStatusChange?: StatusChangeHandler
) => handleStatusChange ?? noopStatusChangeHandler;
export const selectBaseColumns = createSelector( // -------------------------
[selectEnableStatusActions], // Base Columns
enableStatusActions => { // -------------------------
if (!enableStatusActions) {
return TABLE_COLUMNS; const makeSelectBaseColumns = () =>
} createSelector([propsEnableStatusActions], enableStatusActions => {
if (!enableStatusActions) return TABLE_COLUMNS;
return [ return [
...TABLE_COLUMNS, ...TABLE_COLUMNS,
@ -71,15 +60,17 @@ export const selectBaseColumns = createSelector(
filterable: false, filterable: false,
} as GridColDef, } as GridColDef,
]; ];
} });
);
export const selectVisibleColumns = createSelector( // -------------------------
[selectBaseColumns, selectExtraColumns, selectShowExtraColumns], // Visible Columns
// -------------------------
const makeSelectVisibleColumns = () =>
createSelector(
[makeSelectBaseColumns(), propsExtraColumns, propsShowExtraColumns],
(baseColumns, extraColumns, showExtraColumns) => { (baseColumns, extraColumns, showExtraColumns) => {
if (!extraColumns || extraColumns.length === 0) { if (!extraColumns || extraColumns.length === 0) return baseColumns;
return baseColumns;
}
return showExtraColumns return showExtraColumns
? baseColumns ? baseColumns
@ -87,17 +78,26 @@ export const selectVisibleColumns = createSelector(
} }
); );
export const selectResolvedTransactionStatuses = createSelector( // -------------------------
[selectTransactionStatuses], // Resolved Statuses (STATE-based)
statuses => (statuses.length > 0 ? statuses : TRANSACTION_STATUS_FALLBACK) // -------------------------
const makeSelectResolvedStatuses = () =>
createSelector([selectTransactionStatuses], statuses =>
statuses.length > 0 ? statuses : TRANSACTION_STATUS_FALLBACK
); );
export const selectEnhancedColumns = createSelector( // -------------------------
// Enhanced Columns
// -------------------------
export const makeSelectEnhancedColumns = () =>
createSelector(
[ [
selectVisibleColumns, makeSelectVisibleColumns(),
selectLocalRows, propsLocalRows,
selectStatusChangeHandler, propsStatusChangeHandler,
selectResolvedTransactionStatuses, makeSelectResolvedStatuses(),
], ],
( (
visibleColumns, visibleColumns,
@ -106,6 +106,9 @@ export const selectEnhancedColumns = createSelector(
resolvedStatusOptions resolvedStatusOptions
): GridColDef[] => { ): GridColDef[] => {
return visibleColumns.map(col => { return visibleColumns.map(col => {
// --------------------------------
// 1. STATUS COLUMN RENDERER
// --------------------------------
if (col.field === "status") { if (col.field === "status") {
return { return {
...col, ...col,
@ -113,6 +116,7 @@ export const selectEnhancedColumns = createSelector(
const value = params.value?.toLowerCase(); const value = params.value?.toLowerCase();
let bgColor = "#e0e0e0"; let bgColor = "#e0e0e0";
let textColor = "#000"; let textColor = "#000";
switch (value) { switch (value) {
case "completed": case "completed":
bgColor = "#d0f0c0"; bgColor = "#d0f0c0";
@ -131,6 +135,7 @@ export const selectEnhancedColumns = createSelector(
textColor = "#c62828"; textColor = "#c62828";
break; break;
} }
return ( return (
<Box <Box
sx={{ sx={{
@ -153,6 +158,9 @@ export const selectEnhancedColumns = createSelector(
}; };
} }
// --------------------------------
// 2. USER ID COLUMN
// --------------------------------
if (col.field === "userId") { if (col.field === "userId") {
return { return {
...col, ...col,
@ -193,15 +201,20 @@ export const selectEnhancedColumns = createSelector(
}; };
} }
// --------------------------------
// 3. ACTIONS COLUMN
// --------------------------------
if (col.field === "actions") { if (col.field === "actions") {
return { return {
...col, ...col,
renderCell: (params: GridRenderCellParams) => { renderCell: (params: GridRenderCellParams) => {
const currentRow = localRows.find(row => row.id === params.id); const currentRow = localRows.find(row => row.id === params.id);
const options = const options =
currentRow?.options?.map(option => option.value) ?? currentRow?.options?.map(option => option.value) ??
resolvedStatusOptions; resolvedStatusOptions;
const uniqueOptions: string[] = Array.from(new Set(options));
const uniqueOptions = Array.from(new Set(options));
return ( return (
<Select<string> <Select<string>
@ -233,6 +246,6 @@ export const selectEnhancedColumns = createSelector(
} }
return col; return col;
}) as GridColDef[]; });
} }
); );

View File

@ -1,3 +1,4 @@
import { createSelector } from "@reduxjs/toolkit";
import { RootState } from "../store"; import { RootState } from "../store";
import { import {
AdvancedSearchFilters, AdvancedSearchFilters,
@ -11,6 +12,14 @@ export const selectFilters = (state: RootState): AdvancedSearchFilters =>
export const selectPagination = (state: RootState) => export const selectPagination = (state: RootState) =>
state.advancedSearch.pagination; state.advancedSearch.pagination;
export const selectPaginationModel = createSelector(
[selectPagination],
pagination => ({
page: Math.max(0, (pagination.page ?? 1) - 1),
pageSize: pagination.limit ?? 10,
})
);
export const selectSort = (state: RootState) => state.advancedSearch.sort; export const selectSort = (state: RootState) => state.advancedSearch.sort;
export const selectFilterValue = ( export const selectFilterValue = (

View File

@ -33,3 +33,8 @@ export const selectTransactionStatuses = (state: RootState): string[] =>
export const selectNavigationSidebar = (state: RootState): SidebarLink[] => export const selectNavigationSidebar = (state: RootState): SidebarLink[] =>
state.metadata.data?.sidebar?.links ?? []; state.metadata.data?.sidebar?.links ?? [];
export const selectConditionOperators = (
state: RootState
): Record<string, string> | undefined =>
state.metadata.data?.field_names?.conditions;

View File

@ -3,6 +3,7 @@ interface GetAuditsParams {
page?: number; page?: number;
sort?: string; sort?: string;
filter?: string; filter?: string;
entity?: string;
signal?: AbortSignal; signal?: AbortSignal;
} }
@ -11,6 +12,7 @@ export async function getAudits({
page, page,
sort, sort,
filter, filter,
entity,
signal, signal,
}: GetAuditsParams = {}) { }: GetAuditsParams = {}) {
const params = new URLSearchParams(); const params = new URLSearchParams();
@ -19,6 +21,7 @@ export async function getAudits({
if (page) params.set("page", String(page)); if (page) params.set("page", String(page));
if (sort) params.set("sort", sort); if (sort) params.set("sort", sort);
if (filter) params.set("filter", filter); if (filter) params.set("filter", filter);
if (entity) params.set("Entity", entity);
const queryString = params.toString(); const queryString = params.toString();
const response = await fetch( const response = await fetch(