diff --git a/.changeset/gold-starfishes-trade.md b/.changeset/gold-starfishes-trade.md
new file mode 100644
index 000000000..71708e27d
--- /dev/null
+++ b/.changeset/gold-starfishes-trade.md
@@ -0,0 +1,5 @@
+---
+"saleor-dashboard": minor
+---
+
+Introduce datagrid on customer list view
diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json
index feb33a7ab..6869284cd 100644
--- a/locale/defaultMessages.json
+++ b/locale/defaultMessages.json
@@ -691,9 +691,6 @@
"context": "section header button",
"string": "Manage"
},
- "2mRLis": {
- "string": "Search Customer"
- },
"2ob30/": {
"string": "Success! In a few minutes you’ll receive a message with instructions on how to reset your password."
},
@@ -1474,6 +1471,10 @@
"context": "button",
"string": "Back to homepage"
},
+ "945a4a": {
+ "context": "column header",
+ "string": "Customer e-mail"
+ },
"94oZR0": {
"context": "deactivate app billing info",
"string": "You will be still billed for the app."
@@ -1482,9 +1483,6 @@
"context": "button",
"string": "Go back to dashboard"
},
- "97l2MO": {
- "string": "Customer Email"
- },
"98Nw4g": {
"context": "card subtitle",
"string": "Rendered prices"
@@ -2053,6 +2051,10 @@
"context": "no warehouses info",
"string": "There are no warehouses set up for your store. To add stock quantity to the variant please configure a warehouse"
},
+ "D95l71": {
+ "context": "tab name",
+ "string": "All customers"
+ },
"D9Rg+F": {
"context": "window title",
"string": "Channel details"
@@ -2173,9 +2175,6 @@
"E8T3e+": {
"string": "Cannot add and remove group the same time"
},
- "E8VDeH": {
- "string": "No. of Orders"
- },
"E9Dz18": {
"context": "Order summary refunds header",
"string": "Refunds"
@@ -2609,9 +2608,6 @@
"context": "shipping method description",
"string": "Description"
},
- "Gr1SAu": {
- "string": "Customer Name"
- },
"GsBRWL": {
"string": "Languages"
},
@@ -3371,6 +3367,10 @@
"MSItJD": {
"string": "You are about to leave the Dashboard. Do you want to continue?"
},
+ "MTGT8E": {
+ "context": "column header",
+ "string": "No. of orders"
+ },
"MTl5o6": {
"context": "new discount label",
"string": "New discount value"
@@ -6631,6 +6631,9 @@
"kFkPWB": {
"string": "Number"
},
+ "kFsTMN": {
+ "string": "Delete customers"
+ },
"kIvvax": {
"string": "Search Products..."
},
@@ -6709,6 +6712,9 @@
"context": "balance amound missing error message",
"string": "Balance amount is missing"
},
+ "kdRcqU": {
+ "string": "Search customers..."
+ },
"kgVqk1": {
"string": "Category name"
},
@@ -7094,6 +7100,10 @@
"context": "window title",
"string": "Create customer"
},
+ "nZDQbr": {
+ "context": "column header",
+ "string": "Customer name"
+ },
"nayZY0": {
"context": "returned event title",
"string": "Products were returned by"
@@ -8392,10 +8402,6 @@
"context": "title",
"string": "Details"
},
- "xQK2EC": {
- "context": "tab name",
- "string": "All Customers"
- },
"xRbqcg": {
"context": "option",
"string": "Regular product type"
diff --git a/src/categories/views/CategoryList/CategoryList.tsx b/src/categories/views/CategoryList/CategoryList.tsx
index edef04895..abf3330ee 100644
--- a/src/categories/views/CategoryList/CategoryList.tsx
+++ b/src/categories/views/CategoryList/CategoryList.tsx
@@ -59,7 +59,7 @@ export const CategoryList: React.FC = ({ params }) => {
} = useRowSelection(params);
const {
- hasPresetsChange,
+ hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
@@ -164,7 +164,7 @@ export const CategoryList: React.FC = ({ params }) => {
return (
= ({ params }) => {
const {
selectedPreset,
presets,
- hasPresetsChange,
+ hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
@@ -219,7 +219,7 @@ export const CollectionList: React.FC = ({ params }) => {
onFilterChange={changeFilters}
selectedCollectionIds={selectedRowIds}
onSelectCollectionIds={handleSetSelectedCollectionIds}
- hasPresetsChanged={hasPresetsChange}
+ hasPresetsChanged={hasPresetsChanged}
onCollectionsDelete={() =>
openModal("remove", {
ids: selectedRowIds,
diff --git a/src/components/TableFilter/FilterTabs.tsx b/src/components/TableFilter/FilterTabs.tsx
index 363bf4165..5464265c4 100644
--- a/src/components/TableFilter/FilterTabs.tsx
+++ b/src/components/TableFilter/FilterTabs.tsx
@@ -14,7 +14,7 @@ const useStyles = makeStyles(
interface FilterTabsProps {
children?: React.ReactNode;
- currentTab: number;
+ currentTab: number | undefined;
}
export const FilterTabs: React.FC = props => {
diff --git a/src/config.ts b/src/config.ts
index f3df67507..77a313745 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,4 +1,3 @@
-// @ts-strict-ignore
import packageInfo from "../package.json";
import { SearchVariables } from "./hooks/makeSearch";
import { ListSettings, ListViews, Pagination } from "./types";
@@ -7,7 +6,7 @@ export const getAppDefaultUri = () => "/";
export const getAppMountUri = () =>
window?.__SALEOR_CONFIG__?.APP_MOUNT_URI || getAppDefaultUri();
export const getApiUrl = () => window.__SALEOR_CONFIG__.API_URL;
-export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL, 10) || 300;
+export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL ?? "300", 10);
export const IS_CLOUD_INSTANCE =
window.__SALEOR_CONFIG__.IS_CLOUD_INSTANCE === "true";
@@ -80,6 +79,7 @@ export const defaultListSettings: AppListViewSettings = {
},
[ListViews.CUSTOMER_LIST]: {
rowNumber: PAGINATE_BY,
+ columns: ["name", "email", "orders"],
},
[ListViews.DRAFT_LIST]: {
rowNumber: PAGINATE_BY,
diff --git a/src/customers/components/CustomerList/CustomerList.tsx b/src/customers/components/CustomerList/CustomerList.tsx
deleted file mode 100644
index 2d069eeec..000000000
--- a/src/customers/components/CustomerList/CustomerList.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-// @ts-strict-ignore
-import { useUserPermissions } from "@dashboard/auth/hooks/useUserPermissions";
-import Checkbox from "@dashboard/components/Checkbox";
-import RequirePermissions, {
- hasPermissions,
-} from "@dashboard/components/RequirePermissions";
-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 {
- CustomerListUrlSortField,
- customerUrl,
-} from "@dashboard/customers/urls";
-import { ListCustomersQuery, PermissionEnum } from "@dashboard/graphql";
-import { getUserName, renderCollection } from "@dashboard/misc";
-import {
- ListActions,
- ListProps,
- RelayToFlat,
- 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 } from "react-intl";
-
-const useStyles = makeStyles(
- theme => ({
- [theme.breakpoints.up("lg")]: {
- colEmail: {},
- colName: {},
- colOrders: {
- width: 200,
- },
- },
- colEmail: {},
- colName: {
- paddingLeft: 0,
- },
- colOrders: {
- textAlign: "center",
- },
- tableRow: {
- cursor: "pointer",
- },
- }),
- { name: "CustomerList" },
-);
-
-export interface CustomerListProps
- extends ListProps,
- ListActions,
- SortPage {
- customers: RelayToFlat;
-}
-
-const CustomerList: React.FC = props => {
- const {
- settings,
- disabled,
- customers,
- onUpdateListSettings,
- onSort,
- toolbar,
- toggle,
- toggleAll,
- selected,
- sort,
- isChecked,
- } = props;
-
- const userPermissions = useUserPermissions();
-
- const numberOfColumns = hasPermissions(userPermissions, [
- PermissionEnum.MANAGE_ORDERS,
- ])
- ? 4
- : 3;
-
- const classes = useStyles(props);
-
- return (
-
-
- onSort(CustomerListUrlSortField.name)}
- className={classes.colName}
- >
-
-
- onSort(CustomerListUrlSortField.email)}
- className={classes.colEmail}
- >
-
-
-
- onSort(CustomerListUrlSortField.orders)}
- className={classes.colOrders}
- >
-
-
-
-
-
-
-
-
-
-
- {renderCollection(
- customers,
- customer => {
- const isSelected = customer ? isChecked(customer.id) : false;
-
- return (
-
-
- toggle(customer.id)}
- />
-
-
- {getUserName(customer)}
-
-
- {customer?.email ?? }
-
-
-
- {customer?.orders?.totalCount ?? }
-
-
-
- );
- },
- () => (
-
-
-
-
-
- ),
- )}
-
-
- );
-};
-CustomerList.displayName = "CustomerList";
-export default CustomerList;
diff --git a/src/customers/components/CustomerList/index.ts b/src/customers/components/CustomerList/index.ts
deleted file mode 100644
index c5517efd0..000000000
--- a/src/customers/components/CustomerList/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from "./CustomerList";
-export * from "./CustomerList";
diff --git a/src/customers/components/CustomerListDatagrid/CustomerListDatagrid.tsx b/src/customers/components/CustomerListDatagrid/CustomerListDatagrid.tsx
new file mode 100644
index 000000000..8fd7a4555
--- /dev/null
+++ b/src/customers/components/CustomerListDatagrid/CustomerListDatagrid.tsx
@@ -0,0 +1,168 @@
+import { useUserPermissions } from "@dashboard/auth/hooks/useUserPermissions";
+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 { Customer, Customers } from "@dashboard/customers/types";
+import { CustomerListUrlSortField } from "@dashboard/customers/urls";
+import { PermissionEnum } from "@dashboard/graphql";
+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 {
+ createGetCellContent,
+ customerListStaticColumnsAdapter,
+} from "./datagrid";
+import { messages } from "./messages";
+
+interface CustomerListDatagridProps
+ extends ListProps,
+ SortPage {
+ customers: Customers | undefined;
+ loading: boolean;
+ hasRowHover?: boolean;
+ onSelectCustomerIds: (
+ rowsIndex: number[],
+ clearSelection: () => void,
+ ) => void;
+ onRowClick: (id: string) => void;
+ rowAnchor?: (id: string) => string;
+}
+
+export const CustomerListDatagrid = ({
+ customers,
+ sort,
+ loading,
+ settings,
+ onUpdateListSettings,
+ hasRowHover,
+ onRowClick,
+ rowAnchor,
+ disabled,
+ onSelectCustomerIds,
+ onSort,
+}: CustomerListDatagridProps) => {
+ const intl = useIntl();
+ const datagrid = useDatagridChangeState();
+
+ const userPermissions = useUserPermissions();
+ const hasManageOrdersPermission =
+ userPermissions?.some(perm => perm.code === PermissionEnum.MANAGE_ORDERS) ??
+ false;
+
+ const customerListStaticColumns = useMemo(
+ () =>
+ customerListStaticColumnsAdapter(intl, sort, hasManageOrdersPermission),
+ [intl, sort, hasManageOrdersPermission],
+ );
+
+ const onColumnChange = useCallback(
+ (picked: string[]) => {
+ if (onUpdateListSettings) {
+ onUpdateListSettings("columns", picked.filter(Boolean));
+ }
+ },
+ [onUpdateListSettings],
+ );
+
+ const {
+ handlers,
+ visibleColumns,
+ staticColumns,
+ selectedColumns,
+ recentlyAddedColumn,
+ } = useColumns({
+ staticColumns: customerListStaticColumns,
+ selectedColumns: settings?.columns ?? [],
+ onSave: onColumnChange,
+ });
+
+ const getCellContent = useCallback(
+ createGetCellContent({
+ customers,
+ columns: visibleColumns,
+ }),
+ [customers, visibleColumns],
+ );
+
+ const handleRowClick = useCallback(
+ ([_, row]: Item) => {
+ if (!onRowClick || !customers) {
+ return;
+ }
+ const rowData: Customer = customers[row];
+ onRowClick(rowData.id);
+ },
+ [onRowClick, customers],
+ );
+
+ const handleRowAnchor = useCallback(
+ ([, row]: Item) => {
+ if (!rowAnchor || !customers) {
+ return "";
+ }
+ const rowData: Customer = customers[row];
+ return rowAnchor(rowData.id);
+ },
+ [rowAnchor, customers],
+ );
+
+ const handleHeaderClick = useCallback(
+ (col: number) => {
+ const columnName = visibleColumns[col].id as CustomerListUrlSortField;
+
+ onSort(columnName);
+ },
+ [visibleColumns, onSort],
+ );
+
+ return (
+
+ col > 0}
+ rows={customers?.length ?? 0}
+ availableColumns={visibleColumns}
+ emptyText={intl.formatMessage(messages.empty)}
+ onRowSelectionChange={onSelectCustomerIds}
+ getCellContent={getCellContent}
+ getCellError={() => false}
+ selectionActions={() => null}
+ menuItems={() => []}
+ onRowClick={handleRowClick}
+ onHeaderClicked={handleHeaderClick}
+ rowAnchor={handleRowAnchor}
+ recentlyAddedColumn={recentlyAddedColumn}
+ renderColumnPicker={() => (
+
+ )}
+ />
+
+
+
+
+
+ );
+};
diff --git a/src/customers/components/CustomerListDatagrid/datagrid.ts b/src/customers/components/CustomerListDatagrid/datagrid.ts
new file mode 100644
index 000000000..088370fa2
--- /dev/null
+++ b/src/customers/components/CustomerListDatagrid/datagrid.ts
@@ -0,0 +1,69 @@
+import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells";
+import { AvailableColumn } from "@dashboard/components/Datagrid/types";
+import { Customers } from "@dashboard/customers/types";
+import { CustomerListUrlSortField } from "@dashboard/customers/urls";
+import { getUserName } from "@dashboard/misc";
+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 customerListStaticColumnsAdapter = (
+ intl: IntlShape,
+ sort: Sort,
+ includeOrders: boolean,
+): AvailableColumn[] =>
+ [
+ {
+ id: "name",
+ title: intl.formatMessage(columnsMessages.name),
+ width: 450,
+ },
+ {
+ id: "email",
+ title: intl.formatMessage(columnsMessages.email),
+ width: 450,
+ },
+ ...(includeOrders
+ ? [
+ {
+ id: "orders",
+ title: intl.formatMessage(columnsMessages.orders),
+ width: 200,
+ },
+ ]
+ : []),
+ ].map(column => ({
+ ...column,
+ icon: getColumnSortDirectionIcon(sort, column.id),
+ }));
+
+export const createGetCellContent =
+ ({
+ customers,
+ columns,
+ }: {
+ customers: Customers | undefined;
+ columns: AvailableColumn[];
+ }) =>
+ ([column, row]: Item): GridCell => {
+ const rowData = customers?.[row];
+ const columnId = columns[column]?.id;
+
+ if (!columnId || !rowData) {
+ return readonlyTextCell("");
+ }
+
+ switch (columnId) {
+ case "name":
+ return readonlyTextCell(getUserName(rowData) ?? "");
+ case "email":
+ return readonlyTextCell(rowData?.email ?? "");
+ case "orders":
+ return readonlyTextCell(rowData?.orders?.totalCount?.toString() ?? "");
+ default:
+ return readonlyTextCell("");
+ }
+ };
diff --git a/src/customers/components/CustomerListDatagrid/messages.ts b/src/customers/components/CustomerListDatagrid/messages.ts
new file mode 100644
index 000000000..0ea543a71
--- /dev/null
+++ b/src/customers/components/CustomerListDatagrid/messages.ts
@@ -0,0 +1,26 @@
+import { defineMessages } from "react-intl";
+
+export const messages = defineMessages({
+ empty: {
+ id: "FpIcp9",
+ defaultMessage: "No customers found",
+ },
+});
+
+export const columnsMessages = defineMessages({
+ name: {
+ id: "nZDQbr",
+ defaultMessage: "Customer name",
+ description: "column header",
+ },
+ email: {
+ id: "945a4a",
+ defaultMessage: "Customer e-mail",
+ description: "column header",
+ },
+ orders: {
+ id: "MTGT8E",
+ defaultMessage: "No. of orders",
+ description: "column header",
+ },
+});
diff --git a/src/customers/components/CustomerListPage/CustomerListPage.stories.tsx b/src/customers/components/CustomerListPage/CustomerListPage.stories.tsx
index dcc654e15..0b14ef381 100644
--- a/src/customers/components/CustomerListPage/CustomerListPage.stories.tsx
+++ b/src/customers/components/CustomerListPage/CustomerListPage.stories.tsx
@@ -1,11 +1,11 @@
// @ts-strict-ignore
import {
filterPageProps,
+ filterPresetsProps,
listActionsProps,
pageListProps,
searchPageProps,
sortPageProps,
- tabPageProps,
} from "@dashboard/fixtures";
import { Meta, StoryObj } from "@storybook/react";
import React from "react";
@@ -24,7 +24,7 @@ const props: CustomerListPageProps = {
...pageListProps.default,
...searchPageProps,
...sortPageProps,
- ...tabPageProps,
+ ...filterPresetsProps,
customers: customerList,
selectedCustomerIds: ["123"],
filterOpts: {
@@ -47,9 +47,13 @@ const props: CustomerListPageProps = {
...sortPageProps.sort,
sort: CustomerListUrlSortField.name,
},
+ loading: false,
+ hasPresetsChanged: () => false,
+ onSelectCustomerIds: () => undefined,
+ onCustomersDelete: () => undefined,
};
-const CustomerListPage = props => (
+const CustomerListPage = (props: CustomerListPageProps) => (
diff --git a/src/customers/components/CustomerListPage/CustomerListPage.tsx b/src/customers/components/CustomerListPage/CustomerListPage.tsx
index e3758dd08..83c5992e2 100644
--- a/src/customers/components/CustomerListPage/CustomerListPage.tsx
+++ b/src/customers/components/CustomerListPage/CustomerListPage.tsx
@@ -6,76 +6,69 @@ import {
useExtensions,
} from "@dashboard/apps/hooks/useExtensions";
import { useUserPermissions } from "@dashboard/auth/hooks/useUserPermissions";
+import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
-import ButtonWithSelect from "@dashboard/components/ButtonWithSelect";
-import CardMenu from "@dashboard/components/CardMenu/CardMenu";
-import FilterBar from "@dashboard/components/FilterBar";
+import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton";
+import { ButtonWithDropdown } from "@dashboard/components/ButtonWithDropdown";
+import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
+import { Customers } from "@dashboard/customers/types";
import {
customerAddUrl,
CustomerListUrlSortField,
+ customerUrl,
} from "@dashboard/customers/urls";
-import { ListCustomersQuery } from "@dashboard/graphql";
import useNavigator from "@dashboard/hooks/useNavigator";
import { sectionNames } from "@dashboard/intl";
import {
- FilterPageProps,
- ListActions,
+ FilterPagePropsWithPresets,
PageListProps,
- RelayToFlat,
SortPage,
- TabPageProps,
} from "@dashboard/types";
-import { Card } from "@material-ui/core";
-import { makeStyles } from "@saleor/macaw-ui";
-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 CustomerList from "../CustomerList/CustomerList";
+import { CustomerListDatagrid } from "../CustomerListDatagrid/CustomerListDatagrid";
import {
createFilterStructure,
CustomerFilterKeys,
CustomerListFilterOpts,
} from "./filters";
-const useStyles = makeStyles(
- theme => ({
- settings: {
- marginRight: theme.spacing(2),
- },
- }),
- { name: "CustomerListPage" },
-);
-
export interface CustomerListPageProps
extends PageListProps,
- ListActions,
- FilterPageProps,
- SortPage,
- TabPageProps {
- customers: RelayToFlat;
+ FilterPagePropsWithPresets,
+ SortPage {
+ customers: Customers | undefined;
selectedCustomerIds: string[];
+ loading: boolean;
+ onSelectCustomerIds: (rows: number[], clearSelection: () => void) => void;
+ onCustomersDelete: () => void;
}
const CustomerListPage: React.FC = ({
- currentTab,
+ selectedFilterPreset,
filterOpts,
initialSearch,
- onAll,
+ onFilterPresetsAll,
onFilterChange,
+ onFilterPresetDelete,
+ onFilterPresetUpdate,
onSearchChange,
- onTabChange,
- onTabDelete,
- onTabSave,
- tabs,
+ onFilterPresetChange,
+ onFilterPresetPresetSave,
+ filterPresets,
selectedCustomerIds,
+ hasPresetsChanged,
+ onCustomersDelete,
...customerListProps
}) => {
const intl = useIntl();
- const classes = useStyles({});
const navigate = useNavigator();
const userPermissions = useUserPermissions();
const structure = createFilterStructure(intl, filterOpts, userPermissions);
+ const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
const { CUSTOMER_OVERVIEW_CREATE, CUSTOMER_OVERVIEW_MORE_ACTIONS } =
useExtensions(extensionMountPoints.CUSTOMER_LIST);
@@ -87,49 +80,100 @@ const CustomerListPage: React.FC = ({
return (
<>
-
- {extensionMenuItems.length > 0 && (
-
- )}
- navigate(customerAddUrl)}
- options={extensionCreateButtonItems}
- data-test-id="create-customer"
+
+
-
-
+
+
+
+
+
+
+
+ {extensionMenuItems.length > 0 && (
+
+ )}
+ {extensionCreateButtonItems.length > 0 ? (
+ navigate(customerAddUrl)}
+ >
+
+
+ ) : (
+
+ )}
+
+
-
-
+
+ {selectedCustomerIds.length > 0 && (
+
+
+
+ )}
+
+ }
/>
-
-
+ navigate(customerUrl(id))}
+ />
+
>
);
};
diff --git a/src/customers/components/CustomerListPage/filters.ts b/src/customers/components/CustomerListPage/filters.ts
index 4c41b686c..2e17f3700 100644
--- a/src/customers/components/CustomerListPage/filters.ts
+++ b/src/customers/components/CustomerListPage/filters.ts
@@ -54,5 +54,7 @@ export function createFilterStructure(
active: opts.numberOfOrders.active,
permissions: [PermissionEnum.MANAGE_ORDERS],
},
- ].filter(filter => hasPermissions(userPermissions, filter.permissions ?? []));
+ ].filter(filter =>
+ hasPermissions(userPermissions ?? [], filter.permissions ?? []),
+ );
}
diff --git a/src/customers/fixtures.ts b/src/customers/fixtures.ts
index ce0ab5ef1..8df6efd76 100644
--- a/src/customers/fixtures.ts
+++ b/src/customers/fixtures.ts
@@ -1,11 +1,10 @@
-// @ts-strict-ignore
import {
CustomerAddressesQuery,
CustomerDetailsQuery,
- ListCustomersQuery,
PaymentChargeStatusEnum,
} from "@dashboard/graphql";
-import { RelayToFlat } from "@dashboard/types";
+
+import { Customers } from "./types";
export const customers = [
{
@@ -682,7 +681,7 @@ export const customers = [
},
];
-export const customerList: RelayToFlat = [
+export const customerList: Customers = [
{
__typename: "User",
email: "Curtis.bailey@example.com",
@@ -975,20 +974,20 @@ export const customer: CustomerDetailsQuery["user"] &
__typename: "Address",
city: "West Feliciamouth",
cityArea: "Montana",
- companyName: null,
+ companyName: "",
country: {
__typename: "CountryDisplay",
code: "JA",
country: "Japan",
},
- countryArea: null,
+ countryArea: "",
firstName: "Timmy",
id: "33855",
lastName: "Macejkovic",
phone: "+41 460-907-9374",
postalCode: "15926",
streetAddress1: "0238 Cremin Freeway",
- streetAddress2: null,
+ streetAddress2: "",
},
],
dateJoined: "2017-05-07T09:37:30.124154+00:00",
diff --git a/src/customers/types.ts b/src/customers/types.ts
index f7b7e8d55..04c84e36e 100644
--- a/src/customers/types.ts
+++ b/src/customers/types.ts
@@ -1,3 +1,6 @@
+import { ListCustomersQuery } from "@dashboard/graphql";
+import { RelayToFlat } from "@dashboard/types";
+
export interface AddressTypeInput {
city: string;
cityArea?: string;
@@ -29,3 +32,8 @@ export interface AddressType {
streetAddress1: string;
streetAddress2?: string;
}
+
+export type Customers = RelayToFlat<
+ NonNullable
+>;
+export type Customer = Customers[number];
diff --git a/src/customers/views/CustomerList/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx
index f56efea9b..b91eb4cc6 100644
--- a/src/customers/views/CustomerList/CustomerList.tsx
+++ b/src/customers/views/CustomerList/CustomerList.tsx
@@ -1,15 +1,12 @@
-// @ts-strict-ignore
import ActionDialog from "@dashboard/components/ActionDialog";
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
-import SaveFilterTabDialog, {
- SaveFilterTabDialogFormData,
-} from "@dashboard/components/SaveFilterTabDialog";
+import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog";
import { WindowTitle } from "@dashboard/components/WindowTitle";
import {
useBulkRemoveCustomersMutation,
useListCustomersQuery,
} from "@dashboard/graphql";
-import useBulkActions from "@dashboard/hooks/useBulkActions";
+import { useFilterPresets } from "@dashboard/hooks/useFilterPresets";
import useListSettings from "@dashboard/hooks/useListSettings";
import useNavigator from "@dashboard/hooks/useNavigator";
import useNotifier from "@dashboard/hooks/useNotifier";
@@ -18,8 +15,8 @@ import usePaginator, {
createPaginationState,
PaginatorContext,
} from "@dashboard/hooks/usePaginator";
+import { useRowSelection } from "@dashboard/hooks/useRowSelection";
import { commonMessages, sectionNames } from "@dashboard/intl";
-import { maybe } from "@dashboard/misc";
import { ListViews } from "@dashboard/types";
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers";
@@ -27,8 +24,8 @@ import createSortHandler from "@dashboard/utils/handlers/sortHandler";
import { mapEdgesToItems } from "@dashboard/utils/maps";
import { getSortParams } from "@dashboard/utils/sort";
import { DialogContentText } from "@material-ui/core";
-import { DeleteIcon, IconButton } from "@saleor/macaw-ui";
-import React from "react";
+import isEqual from "lodash/isEqual";
+import React, { useCallback } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import CustomerListPage from "../../components/CustomerListPage";
@@ -38,14 +35,11 @@ import {
CustomerListUrlQueryParams,
} from "../../urls";
import {
- deleteFilterTab,
- getActiveFilters,
getFilterOpts,
getFilterQueryParam,
- getFiltersCurrentTab,
- getFilterTabs,
getFilterVariables,
- saveFilterTab,
+ getPresetNameToDelete,
+ storageUtils,
} from "./filters";
import { getSortQueryVariables } from "./sort";
@@ -56,16 +50,36 @@ interface CustomerListProps {
export const CustomerList: React.FC = ({ params }) => {
const navigate = useNavigator();
const notify = useNotifier();
- const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
- params.ids,
- );
+ const intl = useIntl();
const { updateListSettings, settings } = useListSettings(
ListViews.CUSTOMER_LIST,
);
usePaginationReset(customerListUrl, params, settings.rowNumber);
- const intl = useIntl();
+ const {
+ clearRowSelection,
+ selectedRowIds,
+ setClearDatagridRowSelectionCallback,
+ setSelectedRowIds,
+ } = useRowSelection(params);
+
+ const {
+ selectedPreset,
+ presets,
+ hasPresetsChanged,
+ onPresetChange,
+ onPresetDelete,
+ onPresetSave,
+ onPresetUpdate,
+ setPresetIdToDelete,
+ presetIdToDelete,
+ } = useFilterPresets({
+ params,
+ reset: clearRowSelection,
+ getUrl: customerListUrl,
+ storageUtils,
+ });
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
@@ -80,18 +94,16 @@ export const CustomerList: React.FC = ({ params }) => {
displayLoader: true,
variables: queryVariables,
});
-
- const tabs = getFilterTabs();
-
- const currentTab = getFiltersCurrentTab(params, tabs);
+ const customers = mapEdgesToItems(data?.customers);
const [changeFilters, resetFilters, handleSearchChange] =
createFilterHandlers({
- cleanupFn: reset,
+ cleanupFn: clearRowSelection,
createUrl: customerListUrl,
getFilterQueryParam,
navigate,
params,
+ keepActiveTab: true,
});
const [openModal, closeModal] = createDialogActionHandlers<
@@ -99,29 +111,8 @@ export const CustomerList: React.FC = ({ params }) => {
CustomerListUrlQueryParams
>(navigate, customerListUrl, params);
- const handleTabChange = (tab: number) => {
- reset();
- navigate(
- customerListUrl({
- activeTab: tab.toString(),
- ...getFilterTabs()[tab - 1].data,
- }),
- );
- };
-
- const handleTabDelete = () => {
- deleteFilterTab(currentTab);
- reset();
- navigate(customerListUrl());
- };
-
- const handleTabSave = (data: SaveFilterTabDialogFormData) => {
- saveFilterTab(data.name, getActiveFilters(params));
- handleTabChange(tabs.length + 1);
- };
-
const paginationValues = usePaginator({
- pageInfo: maybe(() => data.customers.pageInfo),
+ pageInfo: data?.customers?.pageInfo,
paginationState,
queryString: params,
});
@@ -129,13 +120,13 @@ export const CustomerList: React.FC = ({ params }) => {
const [bulkRemoveCustomers, bulkRemoveCustomersOpts] =
useBulkRemoveCustomersMutation({
onCompleted: data => {
- if (data.customerBulkDelete.errors.length === 0) {
+ if (data.customerBulkDelete?.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges),
});
- reset();
refetch();
+ clearRowSelection();
closeModal();
}
},
@@ -143,53 +134,67 @@ export const CustomerList: React.FC = ({ params }) => {
const handleSort = createSortHandler(navigate, customerListUrl, params);
+ const handleSetSelectedCustomerIds = useCallback(
+ (rows: number[], clearSelection: () => void) => {
+ if (!customers) {
+ return;
+ }
+
+ const rowsIds = rows.map(row => customers[row].id);
+ const haveSaveValues = isEqual(rowsIds, selectedRowIds);
+
+ if (!haveSaveValues) {
+ setSelectedRowIds(rowsIds);
+ }
+
+ setClearDatagridRowSelectionCallback(clearSelection);
+ },
+ [
+ customers,
+ selectedRowIds,
+ setClearDatagridRowSelectionCallback,
+ setSelectedRowIds,
+ ],
+ );
+
return (
openModal("delete-search")}
- onTabSave={() => openModal("save-search")}
- tabs={tabs.map(tab => tab.name)}
- customers={mapEdgesToItems(data?.customers)}
+ onFilterPresetsAll={resetFilters}
+ onFilterPresetChange={onPresetChange}
+ onFilterPresetDelete={(id: number) => {
+ setPresetIdToDelete(id);
+ openModal("delete-search");
+ }}
+ onFilterPresetPresetSave={() => openModal("save-search")}
+ onFilterPresetUpdate={onPresetUpdate}
+ filterPresets={presets.map(preset => preset.name)}
+ customers={customers}
settings={settings}
disabled={loading}
+ loading={loading}
onUpdateListSettings={updateListSettings}
onSort={handleSort}
- toolbar={
-
- openModal("remove", {
- ids: listElements,
- })
- }
- >
-
-
- }
- isChecked={isSelected}
- selected={listElements.length}
- selectedCustomerIds={listElements}
+ selectedCustomerIds={selectedRowIds}
+ onSelectCustomerIds={handleSetSelectedCustomerIds}
sort={getSortParams(params)}
- toggle={toggle}
- toggleAll={toggleAll}
+ hasPresetsChanged={hasPresetsChanged}
+ onCustomersDelete={() => openModal("remove", { ids: selectedRowIds })}
/>
params.ids.length > 0)}
+ open={params.action === "remove" && selectedRowIds?.length > 0}
onClose={closeModal}
confirmButtonState={bulkRemoveCustomersOpts.status}
onConfirm={() =>
bulkRemoveCustomers({
variables: {
- ids: params.ids,
+ ids: selectedRowIds,
},
})
}
@@ -205,10 +210,8 @@ export const CustomerList: React.FC = ({ params }) => {
id="N2SbNc"
defaultMessage="{counter,plural,one{Are you sure you want to delete this customer?} other{Are you sure you want to delete {displayQuantity} customers?}}"
values={{
- counter: maybe(() => params.ids.length),
- displayQuantity: (
- {maybe(() => params.ids.length)}
- ),
+ counter: selectedRowIds?.length,
+ displayQuantity: {selectedRowIds?.length},
}}
/>
@@ -217,14 +220,14 @@ export const CustomerList: React.FC = ({ params }) => {
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
- onSubmit={handleTabSave}
+ onSubmit={onPresetSave}
/>
tabs[currentTab - 1].name, "...")}
+ onSubmit={onPresetDelete}
+ tabName={getPresetNameToDelete(presets, presetIdToDelete)}
/>
);
diff --git a/src/customers/views/CustomerList/filters.ts b/src/customers/views/CustomerList/filters.ts
index 22f90a952..719a793bc 100644
--- a/src/customers/views/CustomerList/filters.ts
+++ b/src/customers/views/CustomerList/filters.ts
@@ -1,15 +1,14 @@
-// @ts-strict-ignore
import { FilterElement } from "@dashboard/components/Filter";
import {
CustomerFilterKeys,
CustomerListFilterOpts,
} from "@dashboard/customers/components/CustomerListPage";
import { CustomerFilterInput } from "@dashboard/graphql";
-import { maybe } from "@dashboard/misc";
import {
createFilterTabUtils,
createFilterUtils,
+ GetFilterTabsOutput,
getGteLteVariables,
getMinMaxQueryParam,
} from "../../../utils/filters";
@@ -26,29 +25,23 @@ export function getFilterOpts(
): CustomerListFilterOpts {
return {
joined: {
- active: maybe(
- () =>
- [params.joinedFrom, params.joinedTo].some(
- field => field !== undefined,
- ),
- false,
- ),
+ active:
+ [params.joinedFrom, params.joinedTo].some(
+ field => field !== undefined,
+ ) ?? false,
value: {
- max: maybe(() => params.joinedTo, ""),
- min: maybe(() => params.joinedFrom, ""),
+ max: params.joinedTo ?? "",
+ min: params.joinedFrom ?? "",
},
},
numberOfOrders: {
- active: maybe(
- () =>
- [params.numberOfOrdersFrom, params.numberOfOrdersTo].some(
- field => field !== undefined,
- ),
- false,
- ),
+ active:
+ [params.numberOfOrdersFrom, params.numberOfOrdersTo].some(
+ field => field !== undefined,
+ ) ?? false,
value: {
- max: maybe(() => params.numberOfOrdersTo, ""),
- min: maybe(() => params.numberOfOrdersFrom, ""),
+ max: params.numberOfOrdersTo ?? "",
+ min: params.numberOfOrdersFrom ?? "",
},
},
};
@@ -63,8 +56,12 @@ export function getFilterVariables(
lte: params.joinedTo,
}),
numberOfOrders: getGteLteVariables({
- gte: parseInt(params.numberOfOrdersFrom, 10),
- lte: parseInt(params.numberOfOrdersTo, 10),
+ gte: params?.numberOfOrdersFrom
+ ? parseInt(params.numberOfOrdersFrom, 10)
+ : null,
+ lte: params?.numberOfOrdersTo
+ ? parseInt(params.numberOfOrdersTo, 10)
+ : null,
}),
search: params.query,
};
@@ -92,10 +89,20 @@ export function getFilterQueryParam(
}
}
-export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
- createFilterTabUtils(CUSTOMER_FILTERS_KEY);
+export const storageUtils = createFilterTabUtils(CUSTOMER_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
createFilterUtils(
CustomerListUrlFiltersEnum,
);
+
+export const getPresetNameToDelete = (
+ presets: GetFilterTabsOutput,
+ presetIdToDelete: number | null,
+): string => {
+ const presetIndex = presetIdToDelete ? presetIdToDelete - 1 : 0;
+ const preset = presets?.[presetIndex];
+ const tabName = preset?.name ?? "...";
+
+ return tabName;
+};
diff --git a/src/discounts/views/SaleList/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx
index daff6e7fb..04e76679f 100644
--- a/src/discounts/views/SaleList/SaleList.tsx
+++ b/src/discounts/views/SaleList/SaleList.tsx
@@ -96,7 +96,7 @@ export const SaleList: React.FC = ({ params }) => {
} = useRowSelection(params);
const {
- hasPresetsChange,
+ hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
@@ -215,7 +215,7 @@ export const SaleList: React.FC = ({ params }) => {
onFilterPresetsAll={resetFilters}
filterPresets={presets.map(preset => preset.name)}
selectedFilterPreset={selectedPreset}
- hasPresetsChanged={hasPresetsChange}
+ hasPresetsChanged={hasPresetsChanged}
onSalesDelete={() => openModal("remove")}
selectedSaleIds={selectedRowIds}
sales={sales}
diff --git a/src/fixtures.ts b/src/fixtures.ts
index 2658a77ee..370a919e2 100644
--- a/src/fixtures.ts
+++ b/src/fixtures.ts
@@ -313,8 +313,8 @@ export const filterPresetsProps: FilterPresetsProps = {
onFilterPresetDelete: () => undefined,
onFilterPresetPresetSave: () => undefined,
onFilterPresetUpdate: () => undefined,
- filterPresets: ["Tab X"],
hasPresetsChanged: () => false,
+ filterPresets: ["Tab X"],
};
export const paginatorContextValues: PaginatorContextValues = {
diff --git a/src/giftCards/GiftCardsList/GiftCardsListHeader/GiftCardsListHeader.tsx b/src/giftCards/GiftCardsList/GiftCardsListHeader/GiftCardsListHeader.tsx
index 58e1fd5d9..bfe062e9b 100644
--- a/src/giftCards/GiftCardsList/GiftCardsListHeader/GiftCardsListHeader.tsx
+++ b/src/giftCards/GiftCardsList/GiftCardsListHeader/GiftCardsListHeader.tsx
@@ -25,7 +25,7 @@ const GiftCardsListHeader: React.FC = () => {
} = useGiftCardListDialogs();
const {
- hasPresetsChange,
+ hasPresetsChanged,
selectedPreset,
presets,
onPresetUpdate,
@@ -57,7 +57,7 @@ const GiftCardsListHeader: React.FC = () => {
{
setPresetIdToDelete(id);
diff --git a/src/hooks/useFilterPresets/useFilterPresets.ts b/src/hooks/useFilterPresets/useFilterPresets.ts
index f64648e21..4fca50fcd 100644
--- a/src/hooks/useFilterPresets/useFilterPresets.ts
+++ b/src/hooks/useFilterPresets/useFilterPresets.ts
@@ -18,7 +18,7 @@ export interface UseFilterPresets {
onPresetDelete: () => void;
onPresetSave: (data: SaveFilterTabDialogFormData) => void;
onPresetUpdate: (tabName: string) => void;
- hasPresetsChange: () => boolean;
+ hasPresetsChanged: () => boolean;
}
export const useFilterPresets = <
@@ -106,7 +106,7 @@ export const useFilterPresets = <
onPresetChange(presets.findIndex(tab => tab.name === tabName) + 1);
};
- const hasPresetsChange = () => {
+ const hasPresetsChanged = () => {
const { parsedQs } = prepareQs(location.search);
if (!selectedPreset) {
@@ -131,6 +131,6 @@ export const useFilterPresets = <
onPresetDelete,
onPresetSave,
onPresetUpdate,
- hasPresetsChange,
+ hasPresetsChanged,
};
};
diff --git a/src/hooks/useRowSelection.ts b/src/hooks/useRowSelection.ts
index 5b0286d0a..4d2fb4d7e 100644
--- a/src/hooks/useRowSelection.ts
+++ b/src/hooks/useRowSelection.ts
@@ -17,10 +17,10 @@ export const useRowSelection = (
const clearDatagridRowSelectionCallback = useRef<(() => void) | null>(null);
const clearRowSelection = () => {
- setSelectedRowIds([]);
if (clearDatagridRowSelectionCallback.current) {
clearDatagridRowSelectionCallback.current();
}
+ setSelectedRowIds([]);
};
const setClearDatagridRowSelectionCallback = (callback: () => void) => {
diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx
index 78900b6f4..5e1acfc3a 100644
--- a/src/orders/views/OrderDraftList/OrderDraftList.tsx
+++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx
@@ -125,7 +125,7 @@ export const OrderDraftList: React.FC = ({ params }) => {
const {
selectedPreset,
presets,
- hasPresetsChange,
+ hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
@@ -221,7 +221,7 @@ export const OrderDraftList: React.FC = ({ params }) => {
onSort={handleSort}
sort={getSortParams(params)}
currencySymbol={channel?.currencyCode}
- hasPresetsChanged={hasPresetsChange}
+ hasPresetsChanged={hasPresetsChanged}
onDraftOrdersDelete={() =>
openModal("remove", {
ids: selectedRowIds,
diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx
index 25de67712..224d9537e 100644
--- a/src/orders/views/OrderList/OrderList.tsx
+++ b/src/orders/views/OrderList/OrderList.tsx
@@ -54,7 +54,7 @@ export const OrderList: React.FC = ({ params }) => {
);
const {
- hasPresetsChange,
+ hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
@@ -163,7 +163,7 @@ export const OrderList: React.FC = ({ params }) => {
onAll={resetFilters}
onSettingsOpen={() => navigate(orderSettingsPath)}
params={params}
- hasPresetsChanged={hasPresetsChange()}
+ hasPresetsChanged={hasPresetsChanged()}
/>
= ({ params }) => {
} = useRowSelection(params);
const {
- hasPresetsChange,
+ hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
@@ -432,7 +432,7 @@ export const ProductList: React.FC = ({ params }) => {
}}
onProductsDelete={() => openModal("delete")}
onTabChange={onPresetChange}
- hasPresetsChanged={hasPresetsChange()}
+ hasPresetsChanged={hasPresetsChanged()}
initialSearch={params.query || ""}
tabs={presets.map(tab => tab.name)}
onExport={() => openModal("export")}
diff --git a/src/types.ts b/src/types.ts
index 2e7fad4f5..8e59a8e4c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -134,7 +134,7 @@ export interface FilterPresetsProps {
}
export interface TabPageProps {
- currentTab: number;
+ currentTab: number | undefined;
tabs: string[];
onAll: () => void;
onTabChange: (tab: number) => void;
diff --git a/tsconfig.json b/tsconfig.json
index 84dfd2330..ef9c40ef5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,7 +21,7 @@
"@locale/*": ["locale/*"],
"@dashboard/*": ["src/*"],
"@test/*": ["testUtils/*"]
- },
+ }
},
"exclude": ["node_modules", "cypress"]
}