Skip to content

Commit

Permalink
added support for bulk edit
Browse files Browse the repository at this point in the history
  • Loading branch information
AnushDeokar committed Mar 2, 2024
1 parent 62becc4 commit 3ff34fa
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 1 deletion.
33 changes: 32 additions & 1 deletion src/atoms/tableScope/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { atom } from "jotai";
import { atomWithStorage, atomWithHash } from "jotai/utils";

import type { PopoverProps } from "@mui/material";
import type { ColumnConfig, TableFilter } from "@src/types/table";
import type {
ColumnConfig,
TableBulkEdit,
TableFilter,
} from "@src/types/table";
import { SEVERITY_LEVELS } from "@src/components/TableModals/CloudLogsModal/CloudLogSeverityIcon";

/**
Expand Down Expand Up @@ -72,6 +76,33 @@ export const tableFiltersPopoverAtom = atom(
}
);

export type BulkEditPopoverState = {
open: boolean;
defaultQuery?: TableBulkEdit;
};

/**
* Store bulk edit popover state.
* Calling the set function resets props.
*
* @example Basic usage:
* ```
* const openBulkEditltersPopover = useSetAtom(bulkEditPopoverAtom, projectScope);
* openBulkEditltersPopover({ query: ... });
* ```
*
* @example Close:
* ```
* openBulkEditltersPopover({ open: false })
* ```
*/
export const bulkEditPopoverAtom = atom(
{ open: false } as BulkEditPopoverState,
(_, set, update?: Partial<BulkEditPopoverState>) => {
set(bulkEditPopoverAtom, { open: true, ...update });
}
);

/** Store whether to show hidden fields (override) in side drawer */
export const sideDrawerShowHiddenFieldsAtom = atomWithStorage(
"__ROWY__SIDE_DRAWER_SHOW_HIDDEN_FIELDS",
Expand Down
189 changes: 189 additions & 0 deletions src/components/TableToolbar/BulkEdit/BulkEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Button, Grid, InputLabel, Stack } from "@mui/material";

import BulkEditPopover from "./BulkEditPopover";
import ColumnSelect from "@src/components/Table/ColumnSelect";
import { TableBulkEdit } from "@src/types/table";
import { useAtom, useSetAtom } from "jotai";
import { useSnackbar } from "notistack";

import {
tableColumnsOrderedAtom,
tableScope,
updateFieldAtom,
} from "@src/atoms/tableScope";
import { generateId } from "@src/utils/table";
import { getFieldType, getFieldProp } from "@src/components/fields";
import { Suspense, createElement, useMemo, useState } from "react";
import { find } from "lodash-es";
import { ErrorBoundary } from "react-error-boundary";
import FieldSkeleton from "@src/components/SideDrawer/FieldSkeleton";
import { InlineErrorFallback } from "@src/components/ErrorFallback";
import { RowSelectionState } from "@tanstack/react-table";

export const NON_EDITABLE_TYPES: string[] = [
"ID",
"CREATED_AT",
"UPDATED_AT",
"UPDATED_BY",
"CREATED_BY",
"COLOR",
"ARRAY_SUB_TABLE",
"SUB_TABLE",
"GEO_POINT",
];

export default function BulkEdit({
selectedRows,
}: {
selectedRows: RowSelectionState;
}) {
const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope);
const updateField = useSetAtom(updateFieldAtom, tableScope);

const { enqueueSnackbar } = useSnackbar();

const filterColumns = useMemo(() => {
return tableColumnsOrdered
.filter((col) => !NON_EDITABLE_TYPES.includes(col.type))
.map((c) => ({
value: c.key,
label: c.name,
type: c.type,
key: c.key,
index: c.index,
config: c.config || {},
}));
}, [tableColumnsOrdered]);

const INITIAL_QUERY: TableBulkEdit | null =
filterColumns.length > 0
? {
key: filterColumns[0].key,
value: "",
id: generateId(),
}
: null;

const [query, setQuery] = useState<TableBulkEdit | null>(INITIAL_QUERY);
const selectedColumn = find(filterColumns, ["key", query?.key]);
const columnType = selectedColumn ? getFieldType(selectedColumn) : null;

const handleColumnChange = (newKey: string | null) => {
const column = find(filterColumns, ["key", newKey]);
if (column && newKey) {
const updatedQuery: TableBulkEdit = {
key: newKey,
value: "",
id: generateId(),
};
setQuery(updatedQuery);
}
};

const handleBulkUpdate = async (): Promise<void> => {
const selectedRowsArr = Object.keys(selectedRows);
try {
const updatePromises = selectedRowsArr.map(async (rowKey) => {
return updateField({
path: rowKey,
fieldName: query?.key ?? "",
value: query?.value,
});
});

await Promise.all(updatePromises);
} catch (e) {
enqueueSnackbar((e as Error).message, { variant: "error" });
}
};

return (
<>
{filterColumns.length > 0 && INITIAL_QUERY && (
<BulkEditPopover>
{({ handleClose }) => (
<div style={{ padding: "20px" }}>
<Grid container spacing={2} sx={{ mb: 2 }}>
<Grid item xs={6}>
<ColumnSelect
multiple={false}
label="Column"
options={filterColumns}
value={query?.key ?? INITIAL_QUERY.key}
onChange={(newKey: string | null) =>
handleColumnChange(newKey ?? INITIAL_QUERY.key)
}
disabled={false}
/>
</Grid>
<Grid item xs={6} key={query?.key}>
{query?.key && (
<ErrorBoundary FallbackComponent={InlineErrorFallback}>
<InputLabel
variant="filled"
id={`filters-label-${query?.key}`}
htmlFor={`sidedrawer-field-${query?.key}`}
>
Value
</InputLabel>

<Suspense fallback={<FieldSkeleton />}>
{columnType &&
createElement(
getFieldProp(
"filter.customInput" as any,
columnType
) || getFieldProp("SideDrawerField", columnType),
{
column: find(filterColumns, ["key", query.key]),
_rowy_ref: {},
value: query.value,
onSubmit: () => {},
onChange: (value: any) => {
const newQuery = {
...query,
value,
};
setQuery(newQuery);
},
}
)}
</Suspense>
</ErrorBoundary>
)}
</Grid>
</Grid>

<Stack
direction="row"
sx={{ "& .MuiButton-root": { minWidth: 100 } }}
justifyContent="center"
spacing={1}
>
<Button
onClick={() => {
handleClose();
}}
>
Cancel
</Button>

<Button
color="primary"
variant="contained"
disabled={query?.value === ""}
onClick={() => {
handleBulkUpdate();
handleClose();
}}
>
Apply
</Button>
</Stack>
</div>
)}
</BulkEditPopover>
)}
</>
);
}
50 changes: 50 additions & 0 deletions src/components/TableToolbar/BulkEdit/BulkEditPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Button, Popover, Tooltip } from "@mui/material";
import EditIcon from "@mui/icons-material/Edit";
import { useAtom } from "jotai";
import { bulkEditPopoverAtom, tableScope } from "@src/atoms/tableScope";
import { useRef } from "react";

export interface IBulkEditPopoverProps {
children: (props: { handleClose: () => void }) => React.ReactNode;
}

export default function BulkEditPopover({ children }: IBulkEditPopoverProps) {
const [{ open }, setBulkEditPopoverState] = useAtom(
bulkEditPopoverAtom,
tableScope
);

const handleClose = () => setBulkEditPopoverState({ open: false });
const anchorEl = useRef<HTMLButtonElement>(null);
const popoverId = open ? "bulkEdit-popover" : undefined;

return (
<>
<Tooltip title="Bulk Edit">
<Button
ref={anchorEl}
variant="outlined"
startIcon={<EditIcon fontSize="small" />}
color="secondary"
onClick={() => setBulkEditPopoverState({ open: true })}
>
Bulk Edit
</Button>
</Tooltip>
<Popover
id={popoverId}
open={open}
anchorEl={anchorEl.current}
onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
transformOrigin={{ vertical: "top", horizontal: "left" }}
sx={{
"& .MuiPaper-root": { width: 440 },
"& .content": { p: 3 },
}}
>
{children({ handleClose })}
</Popover>
</>
);
}
6 changes: 6 additions & 0 deletions src/components/TableToolbar/TableToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const Sort = lazy(() => import("./Sort" /* webpackChunkName: "Filters" */));

// prettier-ignore
const Filters = lazy(() => import("./Filters" /* webpackChunkName: "Filters" */));

// prettier-ignore
const BulkEdit = lazy(() => import("./BulkEdit/BulkEdit" /* webpackChunkName: "Filters" */));

// prettier-ignore
const ImportData = lazy(() => import("./ImportData/ImportData" /* webpackChunkName: "ImportData" */));

Expand Down Expand Up @@ -129,6 +133,8 @@ function RowSelectedToolBar({
Delete
</Button>
</Tooltip>

<BulkEdit selectedRows={selectedRows} />
</StyledStack>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/types/table.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ export type TableFilter = {
id: string;
};

export type TableBulkEdit = {
key: string;
value: any;
id: string;
};

export const TableTools = [
"import",
"export",
Expand Down

0 comments on commit 3ff34fa

Please sign in to comment.