Introduce datagrid on attributes list view (#4040)
This commit is contained in:
parent
f14ba5bcfd
commit
9037c9cfd1
13 changed files with 491 additions and 401 deletions
5
.changeset/loud-countries-kiss.md
Normal file
5
.changeset/loud-countries-kiss.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduce datagrid on attributes list view
|
|
@ -554,9 +554,6 @@
|
||||||
"1X6HtI": {
|
"1X6HtI": {
|
||||||
"string": "All Categories"
|
"string": "All Categories"
|
||||||
},
|
},
|
||||||
"1div9r": {
|
|
||||||
"string": "Search Attribute"
|
|
||||||
},
|
|
||||||
"1gzck6": {
|
"1gzck6": {
|
||||||
"string": "{firstName} {lastName}"
|
"string": "{firstName} {lastName}"
|
||||||
},
|
},
|
||||||
|
@ -1534,6 +1531,9 @@
|
||||||
"context": "section header",
|
"context": "section header",
|
||||||
"string": "Media Information"
|
"string": "Media Information"
|
||||||
},
|
},
|
||||||
|
"9ScmSs": {
|
||||||
|
"string": "Search attributes..."
|
||||||
|
},
|
||||||
"9Sz0By": {
|
"9Sz0By": {
|
||||||
"context": "channel currency",
|
"context": "channel currency",
|
||||||
"string": "Currency"
|
"string": "Currency"
|
||||||
|
@ -2786,6 +2786,10 @@
|
||||||
"HvJPcU": {
|
"HvJPcU": {
|
||||||
"string": "Category deleted"
|
"string": "Category deleted"
|
||||||
},
|
},
|
||||||
|
"I+1KzL": {
|
||||||
|
"context": "tab name",
|
||||||
|
"string": "All attributes"
|
||||||
|
},
|
||||||
"I+UwqI": {
|
"I+UwqI": {
|
||||||
"context": "is filter range or value",
|
"context": "is filter range or value",
|
||||||
"string": "equal to"
|
"string": "equal to"
|
||||||
|
@ -5842,10 +5846,6 @@
|
||||||
"context": "vat included in order price",
|
"context": "vat included in order price",
|
||||||
"string": "VAT included"
|
"string": "VAT included"
|
||||||
},
|
},
|
||||||
"dKPMyh": {
|
|
||||||
"context": "tab name",
|
|
||||||
"string": "All Attributes"
|
|
||||||
},
|
|
||||||
"dM86a2": {
|
"dM86a2": {
|
||||||
"string": "No categories found"
|
"string": "No categories found"
|
||||||
},
|
},
|
||||||
|
@ -6150,6 +6150,9 @@
|
||||||
"g/BrOt": {
|
"g/BrOt": {
|
||||||
"string": "Url has invalid format"
|
"string": "Url has invalid format"
|
||||||
},
|
},
|
||||||
|
"g0GAdN": {
|
||||||
|
"string": "Delete attributes"
|
||||||
|
},
|
||||||
"g3qjSf": {
|
"g3qjSf": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Assign categories"
|
"string": "Assign categories"
|
||||||
|
@ -6952,10 +6955,6 @@
|
||||||
"context": "order refund amount, input label",
|
"context": "order refund amount, input label",
|
||||||
"string": "Amount"
|
"string": "Amount"
|
||||||
},
|
},
|
||||||
"lw9WIk": {
|
|
||||||
"context": "deleted multiple attributes",
|
|
||||||
"string": "Attributes successfully delete"
|
|
||||||
},
|
|
||||||
"lwjzVj": {
|
"lwjzVj": {
|
||||||
"string": "Edit order"
|
"string": "Edit order"
|
||||||
},
|
},
|
||||||
|
@ -8707,6 +8706,10 @@
|
||||||
"context": "hint",
|
"context": "hint",
|
||||||
"string": "Usually ends with /api/manifest"
|
"string": "Usually ends with /api/manifest"
|
||||||
},
|
},
|
||||||
|
"z3GGbZ": {
|
||||||
|
"context": "deleted multiple attributes",
|
||||||
|
"string": "Attributes successfully deleted"
|
||||||
|
},
|
||||||
"z8jo8h": {
|
"z8jo8h": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "View products"
|
"string": "View products"
|
||||||
|
|
|
@ -1,258 +0,0 @@
|
||||||
import {
|
|
||||||
AttributeListUrlSortField,
|
|
||||||
attributeUrl,
|
|
||||||
} from "@dashboard/attributes/urls";
|
|
||||||
import Checkbox from "@dashboard/components/Checkbox";
|
|
||||||
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
|
||||||
import Skeleton from "@dashboard/components/Skeleton";
|
|
||||||
import TableCellHeader from "@dashboard/components/TableCellHeader";
|
|
||||||
import TableHead from "@dashboard/components/TableHead";
|
|
||||||
import { TablePaginationWithContext } from "@dashboard/components/TablePagination";
|
|
||||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
|
||||||
import { AttributeFragment } from "@dashboard/graphql";
|
|
||||||
import { translateBoolean } from "@dashboard/intl";
|
|
||||||
import { renderCollection } from "@dashboard/misc";
|
|
||||||
import { ListActions, ListProps, SortPage } from "@dashboard/types";
|
|
||||||
import { getArrowDirection } from "@dashboard/utils/sort";
|
|
||||||
import { TableBody, TableCell, TableFooter } from "@material-ui/core";
|
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
|
||||||
|
|
||||||
export interface AttributeListProps
|
|
||||||
extends ListProps,
|
|
||||||
ListActions,
|
|
||||||
SortPage<AttributeListUrlSortField> {
|
|
||||||
attributes: AttributeFragment[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
[theme.breakpoints.up("lg")]: {
|
|
||||||
colFaceted: {
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
colName: {
|
|
||||||
width: "auto",
|
|
||||||
},
|
|
||||||
colSearchable: {
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
colSlug: {
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
colVisible: {
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
colFaceted: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
colName: {},
|
|
||||||
colSearchable: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
colSlug: {
|
|
||||||
paddingLeft: 0,
|
|
||||||
},
|
|
||||||
colVisible: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
cursor: "pointer",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "AttributeList" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const numberOfColumns = 6;
|
|
||||||
|
|
||||||
const AttributeList: React.FC<AttributeListProps> = ({
|
|
||||||
attributes,
|
|
||||||
disabled,
|
|
||||||
isChecked,
|
|
||||||
selected,
|
|
||||||
sort,
|
|
||||||
toggle,
|
|
||||||
toggleAll,
|
|
||||||
toolbar,
|
|
||||||
onSort,
|
|
||||||
}) => {
|
|
||||||
const classes = useStyles({});
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ResponsiveTable>
|
|
||||||
<TableHead
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
selected={selected}
|
|
||||||
disabled={disabled}
|
|
||||||
items={attributes}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
toolbar={toolbar}
|
|
||||||
>
|
|
||||||
<TableCellHeader
|
|
||||||
className={classes.colSlug}
|
|
||||||
direction={
|
|
||||||
sort.sort === AttributeListUrlSortField.slug
|
|
||||||
? getArrowDirection(!!sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
arrowPosition="right"
|
|
||||||
onClick={() => onSort(AttributeListUrlSortField.slug)}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="oJkeS6" defaultMessage="Attribute Code" />
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
className={classes.colName}
|
|
||||||
direction={
|
|
||||||
sort.sort === AttributeListUrlSortField.name
|
|
||||||
? getArrowDirection(!!sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClick={() => onSort(AttributeListUrlSortField.name)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="HjUoHK"
|
|
||||||
defaultMessage="Default Label"
|
|
||||||
description="attribute's label'"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
className={classes.colVisible}
|
|
||||||
direction={
|
|
||||||
sort.sort === AttributeListUrlSortField.visible
|
|
||||||
? getArrowDirection(!!sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
textAlign="center"
|
|
||||||
onClick={() => onSort(AttributeListUrlSortField.visible)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="k6WDZl"
|
|
||||||
defaultMessage="Visible"
|
|
||||||
description="attribute is visible"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
className={classes.colSearchable}
|
|
||||||
direction={
|
|
||||||
sort.sort === AttributeListUrlSortField.searchable
|
|
||||||
? getArrowDirection(!!sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
textAlign="center"
|
|
||||||
onClick={() => onSort(AttributeListUrlSortField.searchable)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="yKuba7"
|
|
||||||
defaultMessage="Searchable"
|
|
||||||
description="attribute can be searched in dashboard"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
className={classes.colFaceted}
|
|
||||||
direction={
|
|
||||||
sort.sort === AttributeListUrlSortField.useInFacetedSearch
|
|
||||||
? getArrowDirection(!!sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
textAlign="center"
|
|
||||||
onClick={() => onSort(AttributeListUrlSortField.useInFacetedSearch)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Use as filter"
|
|
||||||
id="Y3pCRX"
|
|
||||||
description="attribute can be searched in storefront"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
</TableHead>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRowLink>
|
|
||||||
<TablePaginationWithContext colSpan={numberOfColumns} />
|
|
||||||
</TableRowLink>
|
|
||||||
</TableFooter>
|
|
||||||
<TableBody>
|
|
||||||
{renderCollection(
|
|
||||||
attributes,
|
|
||||||
attribute => {
|
|
||||||
const isSelected = attribute ? isChecked(attribute.id) : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRowLink
|
|
||||||
selected={isSelected}
|
|
||||||
hover={!!attribute}
|
|
||||||
key={attribute ? attribute.id : "skeleton"}
|
|
||||||
href={attribute && attributeUrl(attribute.id)}
|
|
||||||
className={classes.link}
|
|
||||||
data-test-id={`id-${attribute?.id}`}
|
|
||||||
>
|
|
||||||
<TableCell padding="checkbox">
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
disabled={disabled}
|
|
||||||
disableClickPropagation
|
|
||||||
onChange={() => toggle(attribute?.id ?? "")}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colSlug} data-test-id="slug">
|
|
||||||
{attribute ? attribute.slug : <Skeleton />}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colName} data-test-id="name">
|
|
||||||
{attribute ? attribute.name : <Skeleton />}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
className={classes.colVisible}
|
|
||||||
data-test-id="visible"
|
|
||||||
data-test-visible={attribute?.visibleInStorefront}
|
|
||||||
>
|
|
||||||
{attribute ? (
|
|
||||||
translateBoolean(attribute.visibleInStorefront, intl)
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
className={classes.colSearchable}
|
|
||||||
data-test-id="searchable"
|
|
||||||
data-test-searchable={attribute?.filterableInDashboard}
|
|
||||||
>
|
|
||||||
{attribute ? (
|
|
||||||
translateBoolean(attribute.filterableInDashboard, intl)
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
className={classes.colFaceted}
|
|
||||||
data-test-id="use-in-faceted-search"
|
|
||||||
data-test-use-in-faceted-search={
|
|
||||||
attribute?.filterableInStorefront
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{attribute ? (
|
|
||||||
translateBoolean(attribute.filterableInStorefront, intl)
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => (
|
|
||||||
<TableRowLink>
|
|
||||||
<TableCell colSpan={numberOfColumns}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="ztQgD8"
|
|
||||||
defaultMessage="No attributes found"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</ResponsiveTable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
AttributeList.displayName = "AttributeList";
|
|
||||||
export default AttributeList;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./AttributeList";
|
|
||||||
export * from "./AttributeList";
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
import {
|
||||||
|
AttributeListUrlSortField,
|
||||||
|
attributeUrl,
|
||||||
|
} from "@dashboard/attributes/urls";
|
||||||
|
import { ColumnPicker } from "@dashboard/components/Datagrid/ColumnPicker/ColumnPicker";
|
||||||
|
import { useColumns } from "@dashboard/components/Datagrid/ColumnPicker/useColumns";
|
||||||
|
import Datagrid from "@dashboard/components/Datagrid/Datagrid";
|
||||||
|
import {
|
||||||
|
DatagridChangeStateContext,
|
||||||
|
useDatagridChangeState,
|
||||||
|
} from "@dashboard/components/Datagrid/hooks/useDatagridChange";
|
||||||
|
import { TablePaginationWithContext } from "@dashboard/components/TablePagination";
|
||||||
|
import { AttributeFragment } from "@dashboard/graphql";
|
||||||
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
|
import { ListProps, SortPage } from "@dashboard/types";
|
||||||
|
import { Item } from "@glideapps/glide-data-grid";
|
||||||
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import {
|
||||||
|
attributesListStaticColumnsAdapter,
|
||||||
|
createGetCellContent,
|
||||||
|
} from "./datagrid";
|
||||||
|
import { messages } from "./messages";
|
||||||
|
|
||||||
|
interface AttributeListDatagridProps
|
||||||
|
extends ListProps,
|
||||||
|
SortPage<AttributeListUrlSortField> {
|
||||||
|
attributes: AttributeFragment[];
|
||||||
|
onSelectAttributesIds: (
|
||||||
|
rowsIndex: number[],
|
||||||
|
clearSelection: () => void,
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AttributeListDatagrid = ({
|
||||||
|
attributes,
|
||||||
|
settings,
|
||||||
|
sort,
|
||||||
|
disabled,
|
||||||
|
onSort,
|
||||||
|
onSelectAttributesIds,
|
||||||
|
onUpdateListSettings,
|
||||||
|
}: AttributeListDatagridProps) => {
|
||||||
|
const datagridState = useDatagridChangeState();
|
||||||
|
const navigate = useNavigator();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const attributesListStaticColumns = useMemo(
|
||||||
|
() => attributesListStaticColumnsAdapter(intl, sort),
|
||||||
|
[intl, sort],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onColumnChange = useCallback(
|
||||||
|
(picked: string[]) => {
|
||||||
|
if (onUpdateListSettings) {
|
||||||
|
onUpdateListSettings("columns", picked.filter(Boolean));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onUpdateListSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
handlers,
|
||||||
|
visibleColumns,
|
||||||
|
recentlyAddedColumn,
|
||||||
|
staticColumns,
|
||||||
|
selectedColumns,
|
||||||
|
} = useColumns({
|
||||||
|
selectedColumns: settings?.columns ?? [],
|
||||||
|
staticColumns: attributesListStaticColumns,
|
||||||
|
onSave: onColumnChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCellContent = useCallback(
|
||||||
|
createGetCellContent({
|
||||||
|
attributes,
|
||||||
|
columns: visibleColumns,
|
||||||
|
intl,
|
||||||
|
}),
|
||||||
|
[attributes, intl, visibleColumns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRowClick = useCallback(
|
||||||
|
([_, row]: Item) => {
|
||||||
|
const rowData: AttributeFragment = attributes[row];
|
||||||
|
|
||||||
|
if (rowData) {
|
||||||
|
navigate(attributeUrl(rowData.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[attributes],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRowAnchor = useCallback(
|
||||||
|
([, row]: Item) => attributeUrl(attributes[row].id),
|
||||||
|
[attributes],
|
||||||
|
);
|
||||||
|
const handleHeaderClick = useCallback(
|
||||||
|
(col: number) => {
|
||||||
|
const columnName = visibleColumns[col].id as AttributeListUrlSortField;
|
||||||
|
onSort(columnName);
|
||||||
|
},
|
||||||
|
[visibleColumns, onSort],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatagridChangeStateContext.Provider value={datagridState}>
|
||||||
|
<Datagrid
|
||||||
|
readonly
|
||||||
|
loading={disabled}
|
||||||
|
rowMarkers="checkbox"
|
||||||
|
columnSelect="single"
|
||||||
|
hasRowHover={true}
|
||||||
|
onColumnMoved={handlers.onMove}
|
||||||
|
onColumnResize={handlers.onResize}
|
||||||
|
verticalBorder={col => col > 0}
|
||||||
|
rows={attributes?.length ?? 0}
|
||||||
|
availableColumns={visibleColumns}
|
||||||
|
emptyText={intl.formatMessage(messages.empty)}
|
||||||
|
onRowSelectionChange={onSelectAttributesIds}
|
||||||
|
getCellContent={getCellContent}
|
||||||
|
getCellError={() => false}
|
||||||
|
selectionActions={() => null}
|
||||||
|
menuItems={() => []}
|
||||||
|
onRowClick={handleRowClick}
|
||||||
|
onHeaderClicked={handleHeaderClick}
|
||||||
|
rowAnchor={handleRowAnchor}
|
||||||
|
recentlyAddedColumn={recentlyAddedColumn}
|
||||||
|
renderColumnPicker={() => (
|
||||||
|
<ColumnPicker
|
||||||
|
staticColumns={staticColumns}
|
||||||
|
selectedColumns={selectedColumns}
|
||||||
|
onToggle={handlers.onToggle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box paddingX={6}>
|
||||||
|
<TablePaginationWithContext
|
||||||
|
component="div"
|
||||||
|
settings={settings}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DatagridChangeStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
87
src/attributes/components/AttributeListDatagrid/datagrid.ts
Normal file
87
src/attributes/components/AttributeListDatagrid/datagrid.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import { AttributeListUrlSortField } from "@dashboard/attributes/urls";
|
||||||
|
import { PLACEHOLDER } from "@dashboard/components/Datagrid/const";
|
||||||
|
import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells";
|
||||||
|
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
||||||
|
import { AttributeFragment } from "@dashboard/graphql";
|
||||||
|
import { translateBoolean } from "@dashboard/intl";
|
||||||
|
import { Sort } from "@dashboard/types";
|
||||||
|
import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon";
|
||||||
|
import { GridCell, Item } from "@glideapps/glide-data-grid";
|
||||||
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
|
import { columnsMessages } from "./messages";
|
||||||
|
|
||||||
|
export const attributesListStaticColumnsAdapter = (
|
||||||
|
intl: IntlShape,
|
||||||
|
sort: Sort<AttributeListUrlSortField>,
|
||||||
|
) =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: "slug",
|
||||||
|
title: intl.formatMessage(columnsMessages.slug),
|
||||||
|
width: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "name",
|
||||||
|
title: intl.formatMessage(columnsMessages.name),
|
||||||
|
width: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "visible",
|
||||||
|
title: intl.formatMessage(columnsMessages.visible),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "searchable",
|
||||||
|
title: intl.formatMessage(columnsMessages.searchable),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "use-in-faceted-search",
|
||||||
|
title: intl.formatMessage(columnsMessages.useInFacetedSearch),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
].map(column => ({
|
||||||
|
...column,
|
||||||
|
icon: getColumnSortDirectionIcon(sort, column.id),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const createGetCellContent =
|
||||||
|
({
|
||||||
|
attributes,
|
||||||
|
columns,
|
||||||
|
intl,
|
||||||
|
}: {
|
||||||
|
attributes: AttributeFragment[];
|
||||||
|
columns: AvailableColumn[];
|
||||||
|
intl: IntlShape;
|
||||||
|
}) =>
|
||||||
|
([column, row]: Item): GridCell => {
|
||||||
|
const rowData: AttributeFragment | undefined = attributes[row];
|
||||||
|
const columnId = columns[column]?.id;
|
||||||
|
|
||||||
|
if (!columnId || !rowData) {
|
||||||
|
return readonlyTextCell("");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (columnId) {
|
||||||
|
case "slug":
|
||||||
|
return readonlyTextCell(rowData?.slug ?? PLACEHOLDER);
|
||||||
|
case "name":
|
||||||
|
return readonlyTextCell(rowData?.name ?? PLACEHOLDER);
|
||||||
|
case "visible":
|
||||||
|
return readonlyTextCell(
|
||||||
|
translateBoolean(rowData?.visibleInStorefront, intl),
|
||||||
|
);
|
||||||
|
case "searchable":
|
||||||
|
return readonlyTextCell(
|
||||||
|
translateBoolean(rowData?.filterableInDashboard, intl),
|
||||||
|
);
|
||||||
|
case "use-in-faceted-search":
|
||||||
|
return readonlyTextCell(
|
||||||
|
translateBoolean(rowData?.filterableInStorefront, intl),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return readonlyTextCell("");
|
||||||
|
}
|
||||||
|
};
|
1
src/attributes/components/AttributeListDatagrid/index.ts
Normal file
1
src/attributes/components/AttributeListDatagrid/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./AttributeListDatagrid";
|
35
src/attributes/components/AttributeListDatagrid/messages.ts
Normal file
35
src/attributes/components/AttributeListDatagrid/messages.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const columnsMessages = defineMessages({
|
||||||
|
slug: {
|
||||||
|
id: "oJkeS6",
|
||||||
|
defaultMessage: "Attribute Code",
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
id: "HjUoHK",
|
||||||
|
defaultMessage: "Default Label",
|
||||||
|
description: "attribute's label'",
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
id: "k6WDZl",
|
||||||
|
defaultMessage: "Visible",
|
||||||
|
description: "attribute is visible",
|
||||||
|
},
|
||||||
|
searchable: {
|
||||||
|
id: "yKuba7",
|
||||||
|
defaultMessage: "Searchable",
|
||||||
|
description: "attribute can be searched in dashboard",
|
||||||
|
},
|
||||||
|
useInFacetedSearch: {
|
||||||
|
defaultMessage: "Use as filter",
|
||||||
|
id: "Y3pCRX",
|
||||||
|
description: "attribute can be searched in storefront",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
empty: {
|
||||||
|
id: "ztQgD8",
|
||||||
|
defaultMessage: "No attributes found",
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,26 +1,26 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import {
|
import {
|
||||||
attributeAddUrl,
|
attributeAddUrl,
|
||||||
AttributeListUrlSortField,
|
AttributeListUrlSortField,
|
||||||
} from "@dashboard/attributes/urls";
|
} from "@dashboard/attributes/urls";
|
||||||
|
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton";
|
||||||
import FilterBar from "@dashboard/components/FilterBar";
|
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||||
import { configurationMenuUrl } from "@dashboard/configuration";
|
import { configurationMenuUrl } from "@dashboard/configuration";
|
||||||
import { AttributeFragment } from "@dashboard/graphql";
|
import { AttributeFragment } from "@dashboard/graphql";
|
||||||
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import { sectionNames } from "@dashboard/intl";
|
import { sectionNames } from "@dashboard/intl";
|
||||||
import { Card } from "@material-ui/core";
|
import { Card } from "@material-ui/core";
|
||||||
import React from "react";
|
import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
|
||||||
|
import React, { useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FilterPageProps,
|
FilterPagePropsWithPresets,
|
||||||
ListActions,
|
|
||||||
PageListProps,
|
PageListProps,
|
||||||
SortPage,
|
SortPage,
|
||||||
TabPageProps,
|
|
||||||
} from "../../../types";
|
} from "../../../types";
|
||||||
import AttributeList from "../AttributeList/AttributeList";
|
import { AttributeListDatagrid } from "../AttributeListDatagrid";
|
||||||
import {
|
import {
|
||||||
AttributeFilterKeys,
|
AttributeFilterKeys,
|
||||||
AttributeListFilterOpts,
|
AttributeListFilterOpts,
|
||||||
|
@ -29,11 +29,12 @@ import {
|
||||||
|
|
||||||
export interface AttributeListPageProps
|
export interface AttributeListPageProps
|
||||||
extends PageListProps,
|
extends PageListProps,
|
||||||
ListActions,
|
FilterPagePropsWithPresets<AttributeFilterKeys, AttributeListFilterOpts>,
|
||||||
FilterPageProps<AttributeFilterKeys, AttributeListFilterOpts>,
|
SortPage<AttributeListUrlSortField> {
|
||||||
SortPage<AttributeListUrlSortField>,
|
|
||||||
TabPageProps {
|
|
||||||
attributes: AttributeFragment[];
|
attributes: AttributeFragment[];
|
||||||
|
selectedAttributesIds: string[];
|
||||||
|
onAttributesDelete: () => void;
|
||||||
|
onSelectAttributesIds: (rows: number[], clearSelection: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
|
@ -41,26 +42,65 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
initialSearch,
|
initialSearch,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
currentTab,
|
hasPresetsChanged,
|
||||||
onAll,
|
onFilterPresetChange,
|
||||||
onTabChange,
|
onFilterPresetDelete,
|
||||||
onTabDelete,
|
onFilterPresetPresetSave,
|
||||||
onTabSave,
|
onFilterPresetUpdate,
|
||||||
tabs,
|
onFilterPresetsAll,
|
||||||
|
filterPresets,
|
||||||
|
selectedFilterPreset,
|
||||||
|
onAttributesDelete,
|
||||||
|
selectedAttributesIds,
|
||||||
|
currencySymbol,
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const navigate = useNavigator();
|
||||||
|
|
||||||
const structure = createFilterStructure(intl, filterOpts);
|
const structure = createFilterStructure(intl, filterOpts);
|
||||||
|
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopNav
|
<TopNav
|
||||||
href={configurationMenuUrl}
|
href={configurationMenuUrl}
|
||||||
title={intl.formatMessage(sectionNames.attributes)}
|
title={intl.formatMessage(sectionNames.attributes)}
|
||||||
|
withoutBorder
|
||||||
|
isAlignToRight={false}
|
||||||
>
|
>
|
||||||
|
<Box
|
||||||
|
__flex={1}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Box display="flex">
|
||||||
|
<Box marginX={3} display="flex" alignItems="center">
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FilterPresetsSelect
|
||||||
|
presetsChanged={hasPresetsChanged()}
|
||||||
|
onSelect={onFilterPresetChange}
|
||||||
|
onRemove={onFilterPresetDelete}
|
||||||
|
onUpdate={onFilterPresetUpdate}
|
||||||
|
savedPresets={filterPresets}
|
||||||
|
activePreset={selectedFilterPreset}
|
||||||
|
onSelectAll={onFilterPresetsAll}
|
||||||
|
onSave={onFilterPresetPresetSave}
|
||||||
|
isOpen={isFilterPresetOpen}
|
||||||
|
onOpenChange={setFilterPresetOpen}
|
||||||
|
selectAllLabel={intl.formatMessage({
|
||||||
|
id: "I+1KzL",
|
||||||
|
defaultMessage: "All attributes",
|
||||||
|
description: "tab name",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
<Button
|
<Button
|
||||||
href={attributeAddUrl()}
|
onClick={() => navigate(attributeAddUrl())}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
data-test-id="create-attribute-button"
|
data-test-id="create-attribute-button"
|
||||||
>
|
>
|
||||||
|
@ -70,30 +110,35 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
description="button"
|
description="button"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</TopNav>
|
</TopNav>
|
||||||
<Card>
|
<Card>
|
||||||
<FilterBar
|
<ListFilters<AttributeFilterKeys>
|
||||||
allTabLabel={intl.formatMessage({
|
currencySymbol={currencySymbol}
|
||||||
id: "dKPMyh",
|
|
||||||
defaultMessage: "All Attributes",
|
|
||||||
description: "tab name",
|
|
||||||
})}
|
|
||||||
currentTab={currentTab}
|
|
||||||
filterStructure={structure}
|
|
||||||
initialSearch={initialSearch}
|
initialSearch={initialSearch}
|
||||||
searchPlaceholder={intl.formatMessage({
|
|
||||||
id: "1div9r",
|
|
||||||
defaultMessage: "Search Attribute",
|
|
||||||
})}
|
|
||||||
tabs={tabs}
|
|
||||||
onAll={onAll}
|
|
||||||
onFilterChange={onFilterChange}
|
onFilterChange={onFilterChange}
|
||||||
onSearchChange={onSearchChange}
|
onSearchChange={onSearchChange}
|
||||||
onTabChange={onTabChange}
|
filterStructure={structure}
|
||||||
onTabDelete={onTabDelete}
|
searchPlaceholder={intl.formatMessage({
|
||||||
onTabSave={onTabSave}
|
id: "9ScmSs",
|
||||||
|
defaultMessage: "Search attributes...",
|
||||||
|
})}
|
||||||
|
actions={
|
||||||
|
<Box display="flex" gap={4}>
|
||||||
|
{selectedAttributesIds.length > 0 && (
|
||||||
|
<BulkDeleteButton onClick={onAttributesDelete}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Delete attributes"
|
||||||
|
id="g0GAdN"
|
||||||
/>
|
/>
|
||||||
<AttributeList {...listProps} />
|
</BulkDeleteButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AttributeListDatagrid {...listProps} />
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,39 +1,34 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import {
|
import {
|
||||||
deleteFilterTab,
|
|
||||||
getActiveFilters,
|
|
||||||
getFilterOpts,
|
getFilterOpts,
|
||||||
getFiltersCurrentTab,
|
|
||||||
getFilterTabs,
|
|
||||||
getFilterVariables,
|
getFilterVariables,
|
||||||
saveFilterTab,
|
storageUtils,
|
||||||
} from "@dashboard/attributes/views/AttributeList/filters";
|
} from "@dashboard/attributes/views/AttributeList/filters";
|
||||||
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
||||||
import SaveFilterTabDialog, {
|
import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog";
|
||||||
SaveFilterTabDialogFormData,
|
|
||||||
} from "@dashboard/components/SaveFilterTabDialog";
|
|
||||||
import {
|
import {
|
||||||
useAttributeBulkDeleteMutation,
|
useAttributeBulkDeleteMutation,
|
||||||
useAttributeListQuery,
|
useAttributeListQuery,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
|
import { useFilterPresets } from "@dashboard/hooks/useFilterPresets";
|
||||||
|
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";
|
||||||
|
import { usePaginationReset } from "@dashboard/hooks/usePaginationReset";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
createPaginationState,
|
createPaginationState,
|
||||||
PaginatorContext,
|
PaginatorContext,
|
||||||
} from "@dashboard/hooks/usePaginator";
|
} from "@dashboard/hooks/usePaginator";
|
||||||
|
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
|
||||||
|
import { ListViews } from "@dashboard/types";
|
||||||
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
||||||
import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers";
|
import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers";
|
||||||
import createSortHandler from "@dashboard/utils/handlers/sortHandler";
|
import createSortHandler from "@dashboard/utils/handlers/sortHandler";
|
||||||
import { mapEdgesToItems } from "@dashboard/utils/maps";
|
import { mapEdgesToItems } from "@dashboard/utils/maps";
|
||||||
import { getSortParams } from "@dashboard/utils/sort";
|
import { getSortParams } from "@dashboard/utils/sort";
|
||||||
import { DeleteIcon, IconButton } from "@saleor/macaw-ui";
|
import isEqual from "lodash/isEqual";
|
||||||
import React from "react";
|
import React, { useCallback } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { PAGINATE_BY } from "../../../config";
|
|
||||||
import useBulkActions from "../../../hooks/useBulkActions";
|
|
||||||
import { maybe } from "../../../misc";
|
|
||||||
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
||||||
import AttributeListPage from "../../components/AttributeListPage";
|
import AttributeListPage from "../../components/AttributeListPage";
|
||||||
import {
|
import {
|
||||||
|
@ -51,24 +46,52 @@ interface AttributeListProps {
|
||||||
const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
|
||||||
params.ids,
|
|
||||||
);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
const { updateListSettings, settings } = useListSettings(
|
||||||
|
ListViews.ATTRIBUTE_LIST,
|
||||||
|
);
|
||||||
|
|
||||||
|
usePaginationReset(attributeListUrl, params, settings.rowNumber);
|
||||||
|
|
||||||
|
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
const queryVariables = React.useMemo(
|
const queryVariables = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...paginationState,
|
...paginationState,
|
||||||
filter: getFilterVariables(params),
|
filter: getFilterVariables(params),
|
||||||
sort: getSortQueryVariables(params),
|
sort: getSortQueryVariables(params),
|
||||||
}),
|
}),
|
||||||
[params],
|
[params, settings.rowNumber],
|
||||||
);
|
);
|
||||||
const { data, loading, refetch } = useAttributeListQuery({
|
const { data, loading, refetch } = useAttributeListQuery({
|
||||||
variables: queryVariables,
|
variables: queryVariables,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
clearRowSelection,
|
||||||
|
selectedRowIds,
|
||||||
|
setSelectedRowIds,
|
||||||
|
setClearDatagridRowSelectionCallback,
|
||||||
|
} = useRowSelection(params);
|
||||||
|
|
||||||
|
const {
|
||||||
|
hasPresetsChanged,
|
||||||
|
onPresetChange,
|
||||||
|
onPresetDelete,
|
||||||
|
onPresetSave,
|
||||||
|
onPresetUpdate,
|
||||||
|
selectedPreset,
|
||||||
|
presets,
|
||||||
|
getPresetNameToDelete,
|
||||||
|
setPresetIdToDelete,
|
||||||
|
} = useFilterPresets({
|
||||||
|
getUrl: attributeListUrl,
|
||||||
|
params,
|
||||||
|
storageUtils,
|
||||||
|
reset: clearRowSelection,
|
||||||
|
});
|
||||||
|
|
||||||
const [attributeBulkDelete, attributeBulkDeleteOpts] =
|
const [attributeBulkDelete, attributeBulkDeleteOpts] =
|
||||||
useAttributeBulkDeleteMutation({
|
useAttributeBulkDeleteMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
|
@ -77,21 +100,17 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage({
|
text: intl.formatMessage({
|
||||||
id: "lw9WIk",
|
id: "z3GGbZ",
|
||||||
defaultMessage: "Attributes successfully delete",
|
defaultMessage: "Attributes successfully deleted",
|
||||||
description: "deleted multiple attributes",
|
description: "deleted multiple attributes",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
reset();
|
clearRowSelection();
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const tabs = getFilterTabs();
|
|
||||||
|
|
||||||
const currentTab = getFiltersCurrentTab(params, tabs);
|
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
AttributeListUrlDialog,
|
AttributeListUrlDialog,
|
||||||
AttributeListUrlQueryParams
|
AttributeListUrlQueryParams
|
||||||
|
@ -99,34 +118,14 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
|
|
||||||
const [changeFilters, resetFilters, handleSearchChange] =
|
const [changeFilters, resetFilters, handleSearchChange] =
|
||||||
createFilterHandlers({
|
createFilterHandlers({
|
||||||
cleanupFn: reset,
|
cleanupFn: clearRowSelection,
|
||||||
createUrl: attributeListUrl,
|
createUrl: attributeListUrl,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
navigate,
|
navigate,
|
||||||
params,
|
params,
|
||||||
|
keepActiveTab: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleTabChange = (tab: number) => {
|
|
||||||
reset();
|
|
||||||
navigate(
|
|
||||||
attributeListUrl({
|
|
||||||
activeTab: tab.toString(),
|
|
||||||
...getFilterTabs()[tab - 1].data,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTabDelete = () => {
|
|
||||||
deleteFilterTab(currentTab);
|
|
||||||
reset();
|
|
||||||
navigate(attributeListUrl());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
|
|
||||||
saveFilterTab(data.name, getActiveFilters(params));
|
|
||||||
handleTabChange(tabs.length + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const paginationValues = usePaginator({
|
const paginationValues = usePaginator({
|
||||||
pageInfo: data?.attributes?.pageInfo,
|
pageInfo: data?.attributes?.pageInfo,
|
||||||
paginationState,
|
paginationState,
|
||||||
|
@ -135,64 +134,84 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
||||||
|
|
||||||
|
const attributes = mapEdgesToItems(data?.attributes);
|
||||||
|
|
||||||
|
const handleSelectAttributesIds = useCallback(
|
||||||
|
(rows: number[], clearSelection: () => void) => {
|
||||||
|
if (!attributes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowsIds = rows.map(row => attributes[row]?.id);
|
||||||
|
const haveSaveValues = isEqual(rowsIds, selectedRowIds);
|
||||||
|
|
||||||
|
if (!haveSaveValues) {
|
||||||
|
setSelectedRowIds(rowsIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
setClearDatagridRowSelectionCallback(clearSelection);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
attributes,
|
||||||
|
selectedRowIds,
|
||||||
|
setClearDatagridRowSelectionCallback,
|
||||||
|
setSelectedRowIds,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaginatorContext.Provider value={paginationValues}>
|
<PaginatorContext.Provider value={paginationValues}>
|
||||||
<AttributeListPage
|
<AttributeListPage
|
||||||
attributes={mapEdgesToItems(data?.attributes) ?? []}
|
settings={settings}
|
||||||
currentTab={currentTab}
|
onUpdateListSettings={updateListSettings}
|
||||||
|
onFilterPresetsAll={resetFilters}
|
||||||
|
onFilterPresetDelete={(id: number) => {
|
||||||
|
setPresetIdToDelete(id);
|
||||||
|
openModal("delete-search");
|
||||||
|
}}
|
||||||
|
onFilterPresetPresetSave={() => openModal("save-search")}
|
||||||
|
onFilterPresetChange={onPresetChange}
|
||||||
|
onFilterPresetUpdate={onPresetUpdate}
|
||||||
|
hasPresetsChanged={hasPresetsChanged}
|
||||||
|
onAttributesDelete={() => openModal("remove")}
|
||||||
|
selectedFilterPreset={selectedPreset}
|
||||||
|
selectedAttributesIds={selectedRowIds}
|
||||||
|
filterPresets={presets.map(tab => tab.name)}
|
||||||
|
attributes={attributes ?? []}
|
||||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||||
filterOpts={getFilterOpts(params)}
|
filterOpts={getFilterOpts(params)}
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
isChecked={isSelected}
|
|
||||||
onAll={resetFilters}
|
|
||||||
onFilterChange={changeFilters}
|
onFilterChange={changeFilters}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
onTabChange={handleTabChange}
|
|
||||||
onTabDelete={() => openModal("delete-search")}
|
|
||||||
onTabSave={() => openModal("save-search")}
|
|
||||||
selected={listElements.length}
|
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
tabs={tabs.map(tab => tab.name)}
|
onSelectAttributesIds={handleSelectAttributesIds}
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
toolbar={
|
|
||||||
<IconButton
|
|
||||||
variant="secondary"
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("remove", {
|
|
||||||
ids: listElements,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AttributeBulkDeleteDialog
|
<AttributeBulkDeleteDialog
|
||||||
confirmButtonState={attributeBulkDeleteOpts.status}
|
confirmButtonState={attributeBulkDeleteOpts.status}
|
||||||
open={
|
open={params.action === "remove" && selectedRowIds.length > 0}
|
||||||
params.action === "remove" && !!params.ids && params.ids.length > 0
|
onConfirm={async () => {
|
||||||
}
|
await attributeBulkDelete({ variables: { ids: selectedRowIds } });
|
||||||
onConfirm={() =>
|
clearRowSelection();
|
||||||
attributeBulkDelete({ variables: { ids: params?.ids ?? [] } })
|
}}
|
||||||
}
|
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
quantity={params.ids?.length ?? 0}
|
quantity={selectedRowIds.length}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SaveFilterTabDialog
|
<SaveFilterTabDialog
|
||||||
open={params.action === "save-search"}
|
open={params.action === "save-search"}
|
||||||
confirmButtonState="default"
|
confirmButtonState="default"
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={handleTabSave}
|
onSubmit={onPresetSave}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteFilterTabDialog
|
<DeleteFilterTabDialog
|
||||||
open={params.action === "delete-search"}
|
open={params.action === "delete-search"}
|
||||||
confirmButtonState="default"
|
confirmButtonState="default"
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={handleTabDelete}
|
onSubmit={onPresetDelete}
|
||||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
tabName={getPresetNameToDelete()}
|
||||||
/>
|
/>
|
||||||
</PaginatorContext.Provider>
|
</PaginatorContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -98,8 +98,7 @@ export function getFilterQueryParam(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
|
export const storageUtils = createFilterTabUtils<string>(ATTRIBUTE_FILTERS_KEY);
|
||||||
createFilterTabUtils<AttributeListUrlFilters>(ATTRIBUTE_FILTERS_KEY);
|
|
||||||
|
|
||||||
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
||||||
createFilterUtils<AttributeListUrlQueryParams, AttributeListUrlFilters>(
|
createFilterUtils<AttributeListUrlQueryParams, AttributeListUrlFilters>(
|
||||||
|
|
|
@ -34,6 +34,7 @@ export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
flexWrap="nowrap"
|
flexWrap="nowrap"
|
||||||
|
height="100%"
|
||||||
__flex={isAlignToRight ? "initial" : 1}
|
__flex={isAlignToRight ? "initial" : 1}
|
||||||
>
|
>
|
||||||
{isPickerActive && (
|
{isPickerActive && (
|
||||||
|
|
|
@ -42,6 +42,7 @@ export type ProductListColumns =
|
||||||
export interface AppListViewSettings {
|
export interface AppListViewSettings {
|
||||||
[ListViews.APPS_LIST]: ListSettings;
|
[ListViews.APPS_LIST]: ListSettings;
|
||||||
[ListViews.ATTRIBUTE_VALUE_LIST]: ListSettings;
|
[ListViews.ATTRIBUTE_VALUE_LIST]: ListSettings;
|
||||||
|
[ListViews.ATTRIBUTE_LIST]: ListSettings;
|
||||||
[ListViews.CATEGORY_LIST]: ListSettings;
|
[ListViews.CATEGORY_LIST]: ListSettings;
|
||||||
[ListViews.COLLECTION_LIST]: ListSettings;
|
[ListViews.COLLECTION_LIST]: ListSettings;
|
||||||
[ListViews.CUSTOMER_LIST]: ListSettings;
|
[ListViews.CUSTOMER_LIST]: ListSettings;
|
||||||
|
@ -71,6 +72,10 @@ export const defaultListSettings: AppListViewSettings = {
|
||||||
[ListViews.ATTRIBUTE_VALUE_LIST]: {
|
[ListViews.ATTRIBUTE_VALUE_LIST]: {
|
||||||
rowNumber: 10,
|
rowNumber: 10,
|
||||||
},
|
},
|
||||||
|
[ListViews.ATTRIBUTE_LIST]: {
|
||||||
|
rowNumber: 10,
|
||||||
|
columns: ["slug", "name", "visible", "searchable", "use-in-faceted-search"],
|
||||||
|
},
|
||||||
[ListViews.CATEGORY_LIST]: {
|
[ListViews.CATEGORY_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
columns: ["name", "products", "subcategories"],
|
columns: ["name", "products", "subcategories"],
|
||||||
|
|
Loading…
Reference in a new issue