Product list bulk delete (#3562)
* Datagrid column checkboxes with delete action * Handle product bulk delete * Fix border line between columns * Clear selection after product delete mutation * Handle row selection * Improve handleRowDelete, use clearRowSelection * Fix restart row selection * Cleanup * Fix for delete modal * Fix showing delete modal * Add delete button next to switcher * Extract messages * Fix checking rows in product list datagrid * Check console * Check on row click * Fix fire click when selection checkbox * Simplifie vertiacal border condition * Clear selected product when pagination * Fix issue with columns and row selections * Fix hiding tooltip when modal close * Update storybook props fix accordion use * Fix row selection * Fix clear selection after pagination change * Apply cr changes
This commit is contained in:
parent
86932d1cd9
commit
5eb0be2dc3
17 changed files with 259 additions and 117 deletions
|
@ -6483,6 +6483,9 @@
|
||||||
"context": "VariantDetailsChannelsAvailabilityCard no items available",
|
"context": "VariantDetailsChannelsAvailabilityCard no items available",
|
||||||
"string": "This variant is not available at any of the channels"
|
"string": "This variant is not available at any of the channels"
|
||||||
},
|
},
|
||||||
|
"jrBxCQ": {
|
||||||
|
"string": "Bulk product delete"
|
||||||
|
},
|
||||||
"jswILH": {
|
"jswILH": {
|
||||||
"context": "add attribute as column in product list table",
|
"context": "add attribute as column in product list table",
|
||||||
"string": "Add to Column Options"
|
"string": "Add to Column Options"
|
||||||
|
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -27,7 +27,7 @@
|
||||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@reach/auto-id": "^0.16.0",
|
"@reach/auto-id": "^0.16.0",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.73",
|
"@saleor/macaw-ui": "0.8.0-pre.81",
|
||||||
"@saleor/sdk": "^0.5.0",
|
"@saleor/sdk": "^0.5.0",
|
||||||
"@sentry/react": "^6.0.0",
|
"@sentry/react": "^6.0.0",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
|
@ -7976,9 +7976,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@saleor/macaw-ui": {
|
"node_modules/@saleor/macaw-ui": {
|
||||||
"version": "0.8.0-pre.73",
|
"version": "0.8.0-pre.81",
|
||||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.73.tgz",
|
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.81.tgz",
|
||||||
"integrity": "sha512-7+bFDATTV8ZjX3dpU20z5QMUMSZHoNgidLWEzUBkHWk6EgiDf+V5Mn2t8Eexd34uLOIKCQ0j4f/mVG+slRsj/w==",
|
"integrity": "sha512-6s15zUyn82492DwMDNm0yI6uAzik8t/OlI+LrH23L3EgzcyzprbA7DYzwcV4C7vlCEiMxywLnfS/0b1RYnhM1w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dessert-box/react": "^0.4.0",
|
"@dessert-box/react": "^0.4.0",
|
||||||
"@floating-ui/react-dom-interactions": "^0.5.0",
|
"@floating-ui/react-dom-interactions": "^0.5.0",
|
||||||
|
@ -43372,9 +43372,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@saleor/macaw-ui": {
|
"@saleor/macaw-ui": {
|
||||||
"version": "0.8.0-pre.73",
|
"version": "0.8.0-pre.81",
|
||||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.73.tgz",
|
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.81.tgz",
|
||||||
"integrity": "sha512-7+bFDATTV8ZjX3dpU20z5QMUMSZHoNgidLWEzUBkHWk6EgiDf+V5Mn2t8Eexd34uLOIKCQ0j4f/mVG+slRsj/w==",
|
"integrity": "sha512-6s15zUyn82492DwMDNm0yI6uAzik8t/OlI+LrH23L3EgzcyzprbA7DYzwcV4C7vlCEiMxywLnfS/0b1RYnhM1w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@dessert-box/react": "^0.4.0",
|
"@dessert-box/react": "^0.4.0",
|
||||||
"@floating-ui/react-dom-interactions": "^0.5.0",
|
"@floating-ui/react-dom-interactions": "^0.5.0",
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@reach/auto-id": "^0.16.0",
|
"@reach/auto-id": "^0.16.0",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.73",
|
"@saleor/macaw-ui": "0.8.0-pre.81",
|
||||||
"@saleor/sdk": "^0.5.0",
|
"@saleor/sdk": "^0.5.0",
|
||||||
"@sentry/react": "^6.0.0",
|
"@sentry/react": "^6.0.0",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
|
|
|
@ -79,7 +79,7 @@ export const Attributes: React.FC<AttributesProps> = ({
|
||||||
<Box display="flex" flexDirection="column" gap={5}>
|
<Box display="flex" flexDirection="column" gap={5}>
|
||||||
<Accordion defaultValue="attributes-accordion">
|
<Accordion defaultValue="attributes-accordion">
|
||||||
<Accordion.Item value="attributes-accordion">
|
<Accordion.Item value="attributes-accordion">
|
||||||
<Accordion.Item.Trigger buttonDataTestId="attributes-expand">
|
<Accordion.Trigger buttonDataTestId="attributes-expand">
|
||||||
<Text variant="caption" color="textNeutralSubdued">
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
{...messages.attributesNumber}
|
{...messages.attributesNumber}
|
||||||
|
@ -88,8 +88,8 @@ export const Attributes: React.FC<AttributesProps> = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
</Accordion.Item.Trigger>
|
</Accordion.Trigger>
|
||||||
<Accordion.Item.Content>
|
<Accordion.Content>
|
||||||
{attributes.length > 0 && (
|
{attributes.length > 0 && (
|
||||||
<ul>
|
<ul>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -108,7 +108,7 @@ export const Attributes: React.FC<AttributesProps> = ({
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</Accordion.Item.Content>
|
</Accordion.Content>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -16,13 +16,11 @@ export const ChannelAvailabilityItemWrapper: React.FC<
|
||||||
> = ({ data: { name }, messages, children }) => (
|
> = ({ data: { name }, messages, children }) => (
|
||||||
<Accordion data-test-id="channel-availability-item">
|
<Accordion data-test-id="channel-availability-item">
|
||||||
<Accordion.Item value="channel-availability-item" gap={12}>
|
<Accordion.Item value="channel-availability-item" gap={12}>
|
||||||
<Accordion.Item.Trigger buttonDataTestId="expand-icon">
|
<Accordion.Trigger buttonDataTestId="expand-icon">
|
||||||
<Text variant={"bodyEmp"}>{name}</Text>
|
<Text variant={"bodyEmp"}>{name}</Text>
|
||||||
<Label text={messages.availableDateText} />
|
<Label text={messages.availableDateText} />
|
||||||
</Accordion.Item.Trigger>
|
</Accordion.Trigger>
|
||||||
<Accordion.Item.Content paddingLeft={6}>
|
<Accordion.Content paddingLeft={6}>{children}</Accordion.Content>
|
||||||
{children}
|
|
||||||
</Accordion.Item.Content>
|
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,6 +25,7 @@ import React, {
|
||||||
ReactElement,
|
ReactElement,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
|
@ -54,7 +55,10 @@ import useStyles, {
|
||||||
useFullScreenStyles,
|
useFullScreenStyles,
|
||||||
} from "./styles";
|
} from "./styles";
|
||||||
import { AvailableColumn } from "./types";
|
import { AvailableColumn } from "./types";
|
||||||
import { getDefultColumnPickerProps } from "./utils";
|
import {
|
||||||
|
getDefultColumnPickerProps,
|
||||||
|
preventRowClickOnSelectionCheckbox,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
export interface GetCellContentOpts {
|
export interface GetCellContentOpts {
|
||||||
changes: MutableRefObject<DatagridChange[]>;
|
changes: MutableRefObject<DatagridChange[]>;
|
||||||
|
@ -91,6 +95,7 @@ export interface DatagridProps {
|
||||||
onRowClick?: (item: Item) => void;
|
onRowClick?: (item: Item) => void;
|
||||||
onColumnMoved?: (startIndex: number, endIndex: number) => void;
|
onColumnMoved?: (startIndex: number, endIndex: number) => void;
|
||||||
onColumnResize?: (column: GridColumn, newSize: number) => void;
|
onColumnResize?: (column: GridColumn, newSize: number) => void;
|
||||||
|
onRowSelectionChange?: (rowsId: number[], clearSelection: () => void) => void;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
hasRowHover?: boolean;
|
hasRowHover?: boolean;
|
||||||
rowMarkers?: DataEditorProps["rowMarkers"];
|
rowMarkers?: DataEditorProps["rowMarkers"];
|
||||||
|
@ -126,6 +131,7 @@ export const Datagrid: React.FC<DatagridProps> = ({
|
||||||
loading,
|
loading,
|
||||||
rowAnchor,
|
rowAnchor,
|
||||||
hasRowHover = false,
|
hasRowHover = false,
|
||||||
|
onRowSelectionChange,
|
||||||
...datagridProps
|
...datagridProps
|
||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
@ -151,6 +157,16 @@ export const Datagrid: React.FC<DatagridProps> = ({
|
||||||
const [selection, setSelection] = useState<GridSelection>();
|
const [selection, setSelection] = useState<GridSelection>();
|
||||||
const [hoverRow, setHoverRow] = useState<number | undefined>(undefined);
|
const [hoverRow, setHoverRow] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
|
// Allow to listen to which row is selected and notfiy parent component
|
||||||
|
useEffect(() => {
|
||||||
|
if (onRowSelectionChange && selection) {
|
||||||
|
// Second parameter is callback to clear selection from parent component
|
||||||
|
onRowSelectionChange(Array.from(selection.rows), () => {
|
||||||
|
setSelection(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [onRowSelectionChange, selection]);
|
||||||
|
|
||||||
usePortalClasses({ className: classes.portal });
|
usePortalClasses({ className: classes.portal });
|
||||||
usePreventHistoryBack(scroller);
|
usePreventHistoryBack(scroller);
|
||||||
|
|
||||||
|
@ -242,6 +258,10 @@ export const Datagrid: React.FC<DatagridProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preventRowClickOnSelectionCheckbox(rowMarkers, args.location[0])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
hackARef.current.style.left = `${window.scrollX + args.bounds.x}px`;
|
hackARef.current.style.left = `${window.scrollX + args.bounds.x}px`;
|
||||||
hackARef.current.style.width = `${args.bounds.width}px`;
|
hackARef.current.style.width = `${args.bounds.width}px`;
|
||||||
hackARef.current.style.top = `${window.scrollY + args.bounds.y}px`;
|
hackARef.current.style.top = `${window.scrollY + args.bounds.y}px`;
|
||||||
|
@ -250,20 +270,26 @@ export const Datagrid: React.FC<DatagridProps> = ({
|
||||||
getAppMountUri() + (href.startsWith("/") ? href.slice(1) : href);
|
getAppMountUri() + (href.startsWith("/") ? href.slice(1) : href);
|
||||||
hackARef.current.dataset.reactRouterPath = href;
|
hackARef.current.dataset.reactRouterPath = href;
|
||||||
},
|
},
|
||||||
[hasRowHover, rowAnchor],
|
[hasRowHover, rowAnchor, rowMarkers],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCellClick = useCallback(
|
const handleCellClick = useCallback(
|
||||||
(item: Item, args: CellClickedEventArgs) => {
|
(item: Item, args: CellClickedEventArgs) => {
|
||||||
if (onRowClick && item[0] !== -1) {
|
if (preventRowClickOnSelectionCheckbox(rowMarkers, item[0])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onRowClick) {
|
||||||
onRowClick(item);
|
onRowClick(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRowHover(args);
|
handleRowHover(args);
|
||||||
|
|
||||||
if (hackARef.current) {
|
if (hackARef.current) {
|
||||||
hackARef.current.click();
|
hackARef.current.click();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onRowClick, handleRowHover],
|
[rowMarkers, onRowClick, handleRowHover],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleGridSelectionChange = (gridSelection: GridSelection) => {
|
const handleGridSelectionChange = (gridSelection: GridSelection) => {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { DataEditorProps } from "@glideapps/glide-data-grid";
|
||||||
|
|
||||||
import { ColumnPickerProps } from "../ColumnPicker";
|
import { ColumnPickerProps } from "../ColumnPicker";
|
||||||
|
|
||||||
export const getDefultColumnPickerProps = (
|
export const getDefultColumnPickerProps = (
|
||||||
|
@ -9,3 +11,8 @@ export const getDefultColumnPickerProps = (
|
||||||
hoverOutline: false,
|
hoverOutline: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const preventRowClickOnSelectionCheckbox = (
|
||||||
|
rowMarkers: DataEditorProps["rowMarkers"],
|
||||||
|
location: number,
|
||||||
|
) => !["number", "none"].includes(rowMarkers) && location === -1;
|
||||||
|
|
|
@ -15,12 +15,8 @@ export type TablePaginationWithContextProps = Omit<
|
||||||
export const TablePaginationWithContext = (
|
export const TablePaginationWithContext = (
|
||||||
props: TablePaginationWithContextProps,
|
props: TablePaginationWithContextProps,
|
||||||
) => {
|
) => {
|
||||||
const {
|
const { hasNextPage, hasPreviousPage, paginatorType, ...paginationProps } =
|
||||||
hasNextPage,
|
usePaginatorContext();
|
||||||
hasPreviousPage,
|
|
||||||
paginatorType,
|
|
||||||
...paginationProps
|
|
||||||
} = usePaginatorContext();
|
|
||||||
|
|
||||||
if (paginatorType === "click") {
|
if (paginatorType === "click") {
|
||||||
const { loadNextPage, loadPreviousPage } = paginationProps;
|
const { loadNextPage, loadPreviousPage } = paginationProps;
|
||||||
|
|
|
@ -56,6 +56,7 @@ interface ProductListDatagridProps
|
||||||
SearchAvailableInGridAttributesQuery["availableInGrid"]
|
SearchAvailableInGridAttributesQuery["availableInGrid"]
|
||||||
>;
|
>;
|
||||||
onColumnQueryChange: (query: string) => void;
|
onColumnQueryChange: (query: string) => void;
|
||||||
|
onSelectProductIds: (rowsIndex: number[], clearSelection: () => void) => void;
|
||||||
isAttributeLoading?: boolean;
|
isAttributeLoading?: boolean;
|
||||||
hasRowHover?: boolean;
|
hasRowHover?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +81,7 @@ export const ProductListDatagrid: React.FC<ProductListDatagridProps> = ({
|
||||||
onColumnQueryChange,
|
onColumnQueryChange,
|
||||||
activeAttributeSortId,
|
activeAttributeSortId,
|
||||||
filterDependency,
|
filterDependency,
|
||||||
|
onSelectProductIds,
|
||||||
hasRowHover,
|
hasRowHover,
|
||||||
rowAnchor,
|
rowAnchor,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -103,22 +105,6 @@ export const ProductListDatagrid: React.FC<ProductListDatagridProps> = ({
|
||||||
|
|
||||||
const handleColumnMoved = useCallback(
|
const handleColumnMoved = useCallback(
|
||||||
(startIndex: number, endIndex: number): void => {
|
(startIndex: number, endIndex: number): void => {
|
||||||
// Keep empty column always at beginning
|
|
||||||
if (startIndex === 0) {
|
|
||||||
return setColumns(prevColumns => [...prevColumns]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep empty column always at beginning
|
|
||||||
if (endIndex === 0) {
|
|
||||||
return setColumns(old =>
|
|
||||||
addAtIndex(
|
|
||||||
old[startIndex],
|
|
||||||
removeAtIndex(old, startIndex),
|
|
||||||
endIndex + 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setColumns(old =>
|
setColumns(old =>
|
||||||
addAtIndex(old[startIndex], removeAtIndex(old, startIndex), endIndex),
|
addAtIndex(old[startIndex], removeAtIndex(old, startIndex), endIndex),
|
||||||
);
|
);
|
||||||
|
@ -243,13 +229,12 @@ export const ProductListDatagrid: React.FC<ProductListDatagridProps> = ({
|
||||||
<Datagrid
|
<Datagrid
|
||||||
readonly
|
readonly
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowMarkers="none"
|
rowMarkers="checkbox"
|
||||||
columnSelect="single"
|
columnSelect="single"
|
||||||
freezeColumns={2}
|
|
||||||
hasRowHover={hasRowHover}
|
hasRowHover={hasRowHover}
|
||||||
onColumnMoved={handleColumnMoved}
|
onColumnMoved={handleColumnMoved}
|
||||||
onColumnResize={handleColumnResize}
|
onColumnResize={handleColumnResize}
|
||||||
verticalBorder={col => (col > 1 ? true : false)}
|
verticalBorder={col => col > 0}
|
||||||
getColumnTooltipContent={handleGetColumnTooltipContent}
|
getColumnTooltipContent={handleGetColumnTooltipContent}
|
||||||
availableColumns={columns}
|
availableColumns={columns}
|
||||||
onHeaderClicked={handleHeaderClicked}
|
onHeaderClicked={handleHeaderClicked}
|
||||||
|
@ -258,6 +243,7 @@ export const ProductListDatagrid: React.FC<ProductListDatagridProps> = ({
|
||||||
getCellError={() => false}
|
getCellError={() => false}
|
||||||
menuItems={() => []}
|
menuItems={() => []}
|
||||||
rows={productsLength}
|
rows={productsLength}
|
||||||
|
onRowSelectionChange={onSelectProductIds}
|
||||||
selectionActions={() => null}
|
selectionActions={() => null}
|
||||||
fullScreenTitle={intl.formatMessage(messages.products)}
|
fullScreenTitle={intl.formatMessage(messages.products)}
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useEmptyColumn } from "@dashboard/components/Datagrid/hooks/useEmptyColumn";
|
|
||||||
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
||||||
import { ProductListColumns } from "@dashboard/config";
|
import { ProductListColumns } from "@dashboard/config";
|
||||||
import { GridAttributesQuery } from "@dashboard/graphql";
|
import { GridAttributesQuery } from "@dashboard/graphql";
|
||||||
|
@ -26,7 +25,6 @@ export const useDatagridColumns = ({
|
||||||
settings,
|
settings,
|
||||||
}: UseDatagridColumnsProps) => {
|
}: UseDatagridColumnsProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const emptyColumn = useEmptyColumn();
|
|
||||||
|
|
||||||
const initialColumns = useRef(
|
const initialColumns = useRef(
|
||||||
getColumns({
|
getColumns({
|
||||||
|
@ -35,13 +33,11 @@ export const useDatagridColumns = ({
|
||||||
gridAttributes,
|
gridAttributes,
|
||||||
gridAttributesFromSettings,
|
gridAttributesFromSettings,
|
||||||
activeAttributeSortId,
|
activeAttributeSortId,
|
||||||
emptyColumn,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [columns, setColumns] = useState<AvailableColumn[]>([
|
const [columns, setColumns] = useState<AvailableColumn[]>([
|
||||||
initialColumns.current[0],
|
initialColumns.current[0],
|
||||||
initialColumns.current[1],
|
|
||||||
...initialColumns.current.filter(col =>
|
...initialColumns.current.filter(col =>
|
||||||
settings.columns.includes(col.id as ProductListColumns),
|
settings.columns.includes(col.id as ProductListColumns),
|
||||||
),
|
),
|
||||||
|
@ -86,7 +82,7 @@ function byColumnsInSettingsOrStaticColumns(
|
||||||
) {
|
) {
|
||||||
return (column: AvailableColumn) =>
|
return (column: AvailableColumn) =>
|
||||||
settings.columns.includes(column.id as ProductListColumns) ||
|
settings.columns.includes(column.id as ProductListColumns) ||
|
||||||
["empty", "name"].includes(column.id);
|
["name"].includes(column.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toCurrentColumnData(
|
function toCurrentColumnData(
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { getMoneyRange } from "@dashboard/components/MoneyRange";
|
||||||
import { ProductListColumns } from "@dashboard/config";
|
import { ProductListColumns } from "@dashboard/config";
|
||||||
import { GridAttributesQuery, ProductListQuery } from "@dashboard/graphql";
|
import { GridAttributesQuery, ProductListQuery } from "@dashboard/graphql";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { getDatagridRowDataIndex, isFirstColumn } from "@dashboard/misc";
|
import { getDatagridRowDataIndex } from "@dashboard/misc";
|
||||||
import { ProductListUrlSortField } from "@dashboard/products/urls";
|
import { ProductListUrlSortField } from "@dashboard/products/urls";
|
||||||
import { RelayToFlat, Sort } from "@dashboard/types";
|
import { RelayToFlat, Sort } from "@dashboard/types";
|
||||||
import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon";
|
import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon";
|
||||||
|
@ -34,7 +34,6 @@ interface GetColumnsProps {
|
||||||
gridAttributes: RelayToFlat<GridAttributesQuery["grid"]>;
|
gridAttributes: RelayToFlat<GridAttributesQuery["grid"]>;
|
||||||
gridAttributesFromSettings: ProductListColumns[];
|
gridAttributesFromSettings: ProductListColumns[];
|
||||||
activeAttributeSortId: string;
|
activeAttributeSortId: string;
|
||||||
emptyColumn: AvailableColumn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColumns({
|
export function getColumns({
|
||||||
|
@ -43,10 +42,8 @@ export function getColumns({
|
||||||
gridAttributes,
|
gridAttributes,
|
||||||
gridAttributesFromSettings,
|
gridAttributesFromSettings,
|
||||||
activeAttributeSortId,
|
activeAttributeSortId,
|
||||||
emptyColumn,
|
|
||||||
}: GetColumnsProps): AvailableColumn[] {
|
}: GetColumnsProps): AvailableColumn[] {
|
||||||
return [
|
return [
|
||||||
emptyColumn,
|
|
||||||
{
|
{
|
||||||
id: "name",
|
id: "name",
|
||||||
title: intl.formatMessage(commonMessages.product),
|
title: intl.formatMessage(commonMessages.product),
|
||||||
|
@ -149,10 +146,6 @@ export function createGetCellContent({
|
||||||
[column, row]: Item,
|
[column, row]: Item,
|
||||||
{ changes, getChangeIndex, added, removed }: GetCellContentOpts,
|
{ changes, getChangeIndex, added, removed }: GetCellContentOpts,
|
||||||
) => {
|
) => {
|
||||||
if (isFirstColumn(column)) {
|
|
||||||
return readonlyTextCell("");
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnId = columns[column]?.id;
|
const columnId = columns[column]?.id;
|
||||||
|
|
||||||
if (!columnId) {
|
if (!columnId) {
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { ProductListDeleteButton } from "./ProductListDeleteButton";
|
||||||
|
|
||||||
|
jest.mock("react-intl", () => ({
|
||||||
|
FormattedMessage: ({ defaultMessage }) => <>{defaultMessage}</>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("ProductListDeleteButton", () => {
|
||||||
|
it("should return null when show is equal false", () => {
|
||||||
|
// Arrange & Act
|
||||||
|
const { container } = render(
|
||||||
|
<ProductListDeleteButton show={false} onClick={jest.fn()} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(container).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render button", async () => {
|
||||||
|
// Arrange & Act
|
||||||
|
render(<ProductListDeleteButton show onClick={jest.fn()} />);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(screen.getByRole("button")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fire callback on click", async () => {
|
||||||
|
// Arrange
|
||||||
|
const onClick = jest.fn();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
render(<ProductListDeleteButton show onClick={onClick} />);
|
||||||
|
await userEvent.click(screen.getByRole("button"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Button, Tooltip, TrashBinIcon } from "@saleor/macaw-ui/next";
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
interface ProductListDeleteButtonProps {
|
||||||
|
onClick: () => void;
|
||||||
|
show?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProductListDeleteButton = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ProductListDeleteButtonProps
|
||||||
|
>(({ onClick, show = false }, ref) => {
|
||||||
|
if (!show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<Tooltip.Trigger>
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<TrashBinIcon />}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content side="bottom">
|
||||||
|
<Tooltip.Arrow />
|
||||||
|
<FormattedMessage defaultMessage="Bulk product delete" id="jrBxCQ" />
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
1
src/products/components/ProductListDeleteButton/index.ts
Normal file
1
src/products/components/ProductListDeleteButton/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./ProductListDeleteButton";
|
|
@ -34,6 +34,8 @@ const props: ProductListPageProps = {
|
||||||
...sortPageProps.sort,
|
...sortPageProps.sort,
|
||||||
sort: ProductListUrlSortField.name,
|
sort: ProductListUrlSortField.name,
|
||||||
},
|
},
|
||||||
|
onProductsDelete: () => undefined,
|
||||||
|
onSelectProductIds: () => undefined,
|
||||||
channels: [],
|
channels: [],
|
||||||
columnQuery: "",
|
columnQuery: "",
|
||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
|
@ -53,6 +55,8 @@ const props: ProductListPageProps = {
|
||||||
products,
|
products,
|
||||||
selectedChannelId: "123",
|
selectedChannelId: "123",
|
||||||
selectedProductIds: ["123"],
|
selectedProductIds: ["123"],
|
||||||
|
setBulkDeleteButtonRef: () => undefined,
|
||||||
|
clearRowSelection: () => undefined,
|
||||||
settings: {
|
settings: {
|
||||||
...pageListProps.default.settings,
|
...pageListProps.default.settings,
|
||||||
columns: ["availability", "productType", "price"],
|
columns: ["availability", "productType", "price"],
|
||||||
|
|
|
@ -25,7 +25,6 @@ import {
|
||||||
ChannelProps,
|
ChannelProps,
|
||||||
FetchMoreProps,
|
FetchMoreProps,
|
||||||
FilterPageProps,
|
FilterPageProps,
|
||||||
ListActions,
|
|
||||||
PageListProps,
|
PageListProps,
|
||||||
RelayToFlat,
|
RelayToFlat,
|
||||||
SortPage,
|
SortPage,
|
||||||
|
@ -38,6 +37,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { ProductListUrlSortField, productUrl } from "../../urls";
|
import { ProductListUrlSortField, productUrl } from "../../urls";
|
||||||
import { ProductListDatagrid } from "../ProductListDatagrid";
|
import { ProductListDatagrid } from "../ProductListDatagrid";
|
||||||
|
import { ProductListDeleteButton } from "../ProductListDeleteButton";
|
||||||
import { ProductListTiles } from "../ProductListTiles/ProductListTiles";
|
import { ProductListTiles } from "../ProductListTiles/ProductListTiles";
|
||||||
import { ProductListViewSwitch } from "../ProductListViewSwitch";
|
import { ProductListViewSwitch } from "../ProductListViewSwitch";
|
||||||
import {
|
import {
|
||||||
|
@ -48,7 +48,6 @@ import {
|
||||||
|
|
||||||
export interface ProductListPageProps
|
export interface ProductListPageProps
|
||||||
extends PageListProps<ProductListColumns>,
|
extends PageListProps<ProductListColumns>,
|
||||||
ListActions,
|
|
||||||
Omit<
|
Omit<
|
||||||
FilterPageProps<ProductFilterKeys, ProductListFilterOpts>,
|
FilterPageProps<ProductFilterKeys, ProductListFilterOpts>,
|
||||||
"onTabDelete"
|
"onTabDelete"
|
||||||
|
@ -72,6 +71,10 @@ export interface ProductListPageProps
|
||||||
onColumnQueryChange: (query: string) => void;
|
onColumnQueryChange: (query: string) => void;
|
||||||
onTabUpdate: (tabName: string) => void;
|
onTabUpdate: (tabName: string) => void;
|
||||||
onTabDelete: (tabIndex: number) => void;
|
onTabDelete: (tabIndex: number) => void;
|
||||||
|
onProductsDelete: () => void;
|
||||||
|
onSelectProductIds: (ids: number[], clearSelection: () => void) => void;
|
||||||
|
clearRowSelection: () => void;
|
||||||
|
setBulkDeleteButtonRef: (ref: HTMLButtonElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProductListViewType = "datagrid" | "tile";
|
export type ProductListViewType = "datagrid" | "tile";
|
||||||
|
@ -99,7 +102,6 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onUpdateListSettings,
|
onUpdateListSettings,
|
||||||
selectedChannelId,
|
selectedChannelId,
|
||||||
selectedProductIds,
|
|
||||||
activeAttributeSortId,
|
activeAttributeSortId,
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onTabDelete,
|
onTabDelete,
|
||||||
|
@ -109,6 +111,10 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
tabs,
|
tabs,
|
||||||
onTabUpdate,
|
onTabUpdate,
|
||||||
hasPresetsChanged,
|
hasPresetsChanged,
|
||||||
|
selectedProductIds,
|
||||||
|
onProductsDelete,
|
||||||
|
clearRowSelection,
|
||||||
|
setBulkDeleteButtonRef,
|
||||||
...listProps
|
...listProps
|
||||||
} = props;
|
} = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -133,6 +139,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
"productListViewType",
|
"productListViewType",
|
||||||
DEFAULT_PRODUCT_LIST_VIEW_TYPE,
|
DEFAULT_PRODUCT_LIST_VIEW_TYPE,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDatagridView = storedProductListViewType === "datagrid";
|
const isDatagridView = storedProductListViewType === "datagrid";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -260,10 +267,20 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
defaultMessage: "Search Products...",
|
defaultMessage: "Search Products...",
|
||||||
})}
|
})}
|
||||||
actions={
|
actions={
|
||||||
|
<Box display="flex" gap={7}>
|
||||||
|
<ProductListDeleteButton
|
||||||
|
ref={setBulkDeleteButtonRef}
|
||||||
|
onClick={onProductsDelete}
|
||||||
|
show={selectedProductIds.length > 0}
|
||||||
|
/>
|
||||||
<ProductListViewSwitch
|
<ProductListViewSwitch
|
||||||
defaultValue={storedProductListViewType}
|
defaultValue={storedProductListViewType}
|
||||||
setProductListViewType={setProductListViewType}
|
setProductListViewType={props => {
|
||||||
|
setProductListViewType(props);
|
||||||
|
clearRowSelection();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -27,7 +27,6 @@ import {
|
||||||
useWarehouseListQuery,
|
useWarehouseListQuery,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import useBackgroundTask from "@dashboard/hooks/useBackgroundTask";
|
import useBackgroundTask from "@dashboard/hooks/useBackgroundTask";
|
||||||
import useBulkActions from "@dashboard/hooks/useBulkActions";
|
|
||||||
import useListSettings from "@dashboard/hooks/useListSettings";
|
import useListSettings from "@dashboard/hooks/useListSettings";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import useNotifier from "@dashboard/hooks/useNotifier";
|
import useNotifier from "@dashboard/hooks/useNotifier";
|
||||||
|
@ -64,9 +63,9 @@ import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers";
|
||||||
import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps";
|
import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps";
|
||||||
import { getSortUrlVariables } from "@dashboard/utils/sort";
|
import { getSortUrlVariables } from "@dashboard/utils/sort";
|
||||||
import { DialogContentText } from "@material-ui/core";
|
import { DialogContentText } from "@material-ui/core";
|
||||||
import { DeleteIcon, IconButton } from "@saleor/macaw-ui";
|
import isEqual from "lodash/isEqual";
|
||||||
import { stringify } from "qs";
|
import { stringify } from "qs";
|
||||||
import React, { useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { useSortRedirects } from "../../../hooks/useSortRedirects";
|
import { useSortRedirects } from "../../../hooks/useSortRedirects";
|
||||||
|
@ -98,14 +97,34 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
const { queue } = useBackgroundTask();
|
const { queue } = useBackgroundTask();
|
||||||
|
|
||||||
const [tabIndexToDelete, setTabIndexToDelete] = useState<number | null>(null);
|
const [tabIndexToDelete, setTabIndexToDelete] = useState<number | null>(null);
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const [selectedProductIds, setSelectedProductIds] = useState<string[]>([]);
|
||||||
[],
|
const deleteButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
);
|
|
||||||
|
|
||||||
const { updateListSettings, settings } = useListSettings<ProductListColumns>(
|
const { updateListSettings, settings } = useListSettings<ProductListColumns>(
|
||||||
ListViews.PRODUCT_LIST,
|
ListViews.PRODUCT_LIST,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Keep reference to clear datagrid selection function
|
||||||
|
const clearRowSelectionCallback = React.useRef<() => void | null>(null);
|
||||||
|
const clearRowSelection = () => {
|
||||||
|
setSelectedProductIds([]);
|
||||||
|
if (clearRowSelectionCallback.current) {
|
||||||
|
clearRowSelectionCallback.current();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Whenever pagination change we need to clear datagrid selection
|
||||||
|
useEffect(() => {
|
||||||
|
clearRowSelection();
|
||||||
|
}, [params.after, params.before]);
|
||||||
|
|
||||||
|
// Remove focus from delete button after delete action
|
||||||
|
useEffect(() => {
|
||||||
|
if (!params.action && deleteButtonRef.current) {
|
||||||
|
deleteButtonRef.current.blur();
|
||||||
|
}
|
||||||
|
}, [params.action]);
|
||||||
|
|
||||||
usePaginationReset(productListUrl, params, settings.rowNumber);
|
usePaginationReset(productListUrl, params, settings.rowNumber);
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -223,14 +242,30 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
id: data.exportProducts.exportFile.id,
|
id: data.exportProducts.exportFile.id,
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
reset();
|
clearRowSelection();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [productBulkDelete, productBulkDeleteOpts] =
|
||||||
|
useProductBulkDeleteMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productBulkDelete.errors.length === 0) {
|
||||||
|
closeModal();
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges),
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
limitOpts.refetch();
|
||||||
|
clearRowSelection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [changeFilters, resetFilters, handleSearchChange] =
|
const [changeFilters, resetFilters, handleSearchChange] =
|
||||||
createFilterHandlers({
|
createFilterHandlers({
|
||||||
cleanupFn: reset,
|
cleanupFn: clearRowSelection,
|
||||||
createUrl: productListUrl,
|
createUrl: productListUrl,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
navigate,
|
navigate,
|
||||||
|
@ -239,7 +274,8 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleTabChange = (tab: number) => {
|
const handleTabChange = (tab: number) => {
|
||||||
reset();
|
clearRowSelection();
|
||||||
|
|
||||||
const qs = new URLSearchParams(getFilterTabs()[tab - 1]?.data ?? "");
|
const qs = new URLSearchParams(getFilterTabs()[tab - 1]?.data ?? "");
|
||||||
qs.append("activeTab", tab.toString());
|
qs.append("activeTab", tab.toString());
|
||||||
|
|
||||||
|
@ -248,7 +284,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
|
|
||||||
const handleFilterTabDelete = () => {
|
const handleFilterTabDelete = () => {
|
||||||
deleteFilterTab(tabIndexToDelete);
|
deleteFilterTab(tabIndexToDelete);
|
||||||
reset();
|
clearRowSelection();
|
||||||
|
|
||||||
// When deleting the current tab, navigate to the All products
|
// When deleting the current tab, navigate to the All products
|
||||||
if (tabIndexToDelete === currentTab) {
|
if (tabIndexToDelete === currentTab) {
|
||||||
|
@ -298,6 +334,14 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSubmitBulkDelete = () => {
|
||||||
|
productBulkDelete({
|
||||||
|
variables: { ids: selectedProductIds },
|
||||||
|
});
|
||||||
|
deleteButtonRef.current.blur();
|
||||||
|
clearRowSelection();
|
||||||
|
};
|
||||||
|
|
||||||
const kindOpts = getProductKindOpts(availableProductKinds, intl);
|
const kindOpts = getProductKindOpts(availableProductKinds, intl);
|
||||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
const channelOpts = availableChannels
|
const channelOpts = availableChannels
|
||||||
|
@ -330,6 +374,26 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const products = mapEdgesToItems(data?.products);
|
||||||
|
|
||||||
|
const handleSetSelectedProductIds = useCallback(
|
||||||
|
(rows: number[], clearSelection: () => void) => {
|
||||||
|
if (!products) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowsIds = rows.map(row => products[row].id);
|
||||||
|
const haveSaveValues = isEqual(rowsIds, selectedProductIds);
|
||||||
|
|
||||||
|
if (!haveSaveValues) {
|
||||||
|
setSelectedProductIds(rowsIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearRowSelectionCallback.current = clearSelection;
|
||||||
|
},
|
||||||
|
[products, selectedProductIds],
|
||||||
|
);
|
||||||
|
|
||||||
const availableInGridAttributesOpts = useAvailableInGridAttributesSearch({
|
const availableInGridAttributesOpts = useAvailableInGridAttributesSearch({
|
||||||
variables: {
|
variables: {
|
||||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||||
|
@ -341,22 +405,6 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
skip: filteredColumnIds.length === 0,
|
skip: filteredColumnIds.length === 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [productBulkDelete, productBulkDeleteOpts] =
|
|
||||||
useProductBulkDeleteMutation({
|
|
||||||
onCompleted: data => {
|
|
||||||
if (data.productBulkDelete.errors.length === 0) {
|
|
||||||
closeModal();
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges),
|
|
||||||
});
|
|
||||||
reset();
|
|
||||||
refetch();
|
|
||||||
limitOpts.refetch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loadMore: loadMoreDialogProductTypes,
|
loadMore: loadMoreDialogProductTypes,
|
||||||
search: searchDialogProductTypes,
|
search: searchDialogProductTypes,
|
||||||
|
@ -441,26 +489,15 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
)}
|
)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
limits={limitOpts.data?.shop.limits}
|
limits={limitOpts.data?.shop.limits}
|
||||||
products={mapEdgesToItems(data?.products)}
|
products={products}
|
||||||
selectedProductIds={listElements}
|
|
||||||
onColumnQueryChange={availableInGridAttributesOpts.search}
|
onColumnQueryChange={availableInGridAttributesOpts.search}
|
||||||
onFetchMore={availableInGridAttributesOpts.loadMore}
|
onFetchMore={availableInGridAttributesOpts.loadMore}
|
||||||
onUpdateListSettings={updateListSettings}
|
onUpdateListSettings={(...props) => {
|
||||||
|
clearRowSelection();
|
||||||
|
updateListSettings(...props);
|
||||||
|
}}
|
||||||
onAdd={() => openModal("create-product")}
|
onAdd={() => openModal("create-product")}
|
||||||
onAll={resetFilters}
|
onAll={resetFilters}
|
||||||
toolbar={
|
|
||||||
<IconButton
|
|
||||||
variant="secondary"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => openModal("delete")}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
onFilterChange={changeFilters}
|
onFilterChange={changeFilters}
|
||||||
onFilterAttributeFocus={setFocusedAttribute}
|
onFilterAttributeFocus={setFocusedAttribute}
|
||||||
|
@ -470,23 +507,26 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
setTabIndexToDelete(tabIndex);
|
setTabIndexToDelete(tabIndex);
|
||||||
openModal("delete-search");
|
openModal("delete-search");
|
||||||
}}
|
}}
|
||||||
|
onProductsDelete={() => openModal("delete")}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
hasPresetsChanged={hasPresetsChanged()}
|
hasPresetsChanged={hasPresetsChanged()}
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
tabs={tabs.map(tab => tab.name)}
|
tabs={tabs.map(tab => tab.name)}
|
||||||
onExport={() => openModal("export")}
|
onExport={() => openModal("export")}
|
||||||
selectedChannelId={selectedChannel?.id}
|
selectedChannelId={selectedChannel?.id}
|
||||||
|
selectedProductIds={selectedProductIds}
|
||||||
|
onSelectProductIds={handleSetSelectedProductIds}
|
||||||
columnQuery={availableInGridAttributesOpts.query}
|
columnQuery={availableInGridAttributesOpts.query}
|
||||||
|
clearRowSelection={clearRowSelection}
|
||||||
|
setBulkDeleteButtonRef={(ref: HTMLButtonElement) => {
|
||||||
|
deleteButtonRef.current = ref;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ActionDialog
|
<ActionDialog
|
||||||
open={params.action === "delete"}
|
open={params.action === "delete"}
|
||||||
confirmButtonState={productBulkDeleteOpts.status}
|
confirmButtonState={productBulkDeleteOpts.status}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={() => {
|
onConfirm={handleSubmitBulkDelete}
|
||||||
productBulkDelete({
|
|
||||||
variables: { ids: listElements },
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
id: "F4WdSO",
|
id: "F4WdSO",
|
||||||
defaultMessage: "Delete Products",
|
defaultMessage: "Delete Products",
|
||||||
|
@ -500,8 +540,8 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}"
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}"
|
||||||
description="dialog content"
|
description="dialog content"
|
||||||
values={{
|
values={{
|
||||||
counter: params?.ids?.length,
|
counter: selectedProductIds.length,
|
||||||
displayQuantity: <strong>{params?.ids?.length}</strong>,
|
displayQuantity: <strong>{selectedProductIds.length}</strong>,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
@ -525,7 +565,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
all: countAllProducts.data?.products?.totalCount,
|
all: countAllProducts.data?.products?.totalCount,
|
||||||
filter: data?.products?.totalCount,
|
filter: data?.products?.totalCount,
|
||||||
}}
|
}}
|
||||||
selectedProducts={listElements.length}
|
selectedProducts={selectedProductIds.length}
|
||||||
warehouses={mapEdgesToItems(warehouses?.data?.warehouses) || []}
|
warehouses={mapEdgesToItems(warehouses?.data?.warehouses) || []}
|
||||||
channels={availableChannels}
|
channels={availableChannels}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
|
@ -535,7 +575,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
input: {
|
input: {
|
||||||
...data,
|
...data,
|
||||||
filter,
|
filter,
|
||||||
ids: listElements,
|
ids: selectedProductIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue