Customer list datagrid (#3940)
* Add default columns to customers list view * Customer list datagrid implementation * Fix types * Remove old code * Migrate some of the files to strict null checks * Extract messages * Add changeset * Drop Tabs * Fix filter presets typo * Reuse bulk delete button * Fix crashing presets * Enlarge columns * Extract to a separat function * Fix race condition * Fix type post-merge
This commit is contained in:
parent
2491055292
commit
a333adbb43
28 changed files with 563 additions and 423 deletions
5
.changeset/gold-starfishes-trade.md
Normal file
5
.changeset/gold-starfishes-trade.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduce datagrid on customer list view
|
|
@ -691,9 +691,6 @@
|
||||||
"context": "section header button",
|
"context": "section header button",
|
||||||
"string": "Manage"
|
"string": "Manage"
|
||||||
},
|
},
|
||||||
"2mRLis": {
|
|
||||||
"string": "Search Customer"
|
|
||||||
},
|
|
||||||
"2ob30/": {
|
"2ob30/": {
|
||||||
"string": "Success! In a few minutes you’ll receive a message with instructions on how to reset your password."
|
"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",
|
"context": "button",
|
||||||
"string": "Back to homepage"
|
"string": "Back to homepage"
|
||||||
},
|
},
|
||||||
|
"945a4a": {
|
||||||
|
"context": "column header",
|
||||||
|
"string": "Customer e-mail"
|
||||||
|
},
|
||||||
"94oZR0": {
|
"94oZR0": {
|
||||||
"context": "deactivate app billing info",
|
"context": "deactivate app billing info",
|
||||||
"string": "You will be still billed for the app."
|
"string": "You will be still billed for the app."
|
||||||
|
@ -1482,9 +1483,6 @@
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Go back to dashboard"
|
"string": "Go back to dashboard"
|
||||||
},
|
},
|
||||||
"97l2MO": {
|
|
||||||
"string": "Customer Email"
|
|
||||||
},
|
|
||||||
"98Nw4g": {
|
"98Nw4g": {
|
||||||
"context": "card subtitle",
|
"context": "card subtitle",
|
||||||
"string": "Rendered prices"
|
"string": "Rendered prices"
|
||||||
|
@ -2053,6 +2051,10 @@
|
||||||
"context": "no warehouses info",
|
"context": "no warehouses info",
|
||||||
"string": "There are no warehouses set up for your store. To add stock quantity to the variant please <a>configure a warehouse</a>"
|
"string": "There are no warehouses set up for your store. To add stock quantity to the variant please <a>configure a warehouse</a>"
|
||||||
},
|
},
|
||||||
|
"D95l71": {
|
||||||
|
"context": "tab name",
|
||||||
|
"string": "All customers"
|
||||||
|
},
|
||||||
"D9Rg+F": {
|
"D9Rg+F": {
|
||||||
"context": "window title",
|
"context": "window title",
|
||||||
"string": "Channel details"
|
"string": "Channel details"
|
||||||
|
@ -2173,9 +2175,6 @@
|
||||||
"E8T3e+": {
|
"E8T3e+": {
|
||||||
"string": "Cannot add and remove group the same time"
|
"string": "Cannot add and remove group the same time"
|
||||||
},
|
},
|
||||||
"E8VDeH": {
|
|
||||||
"string": "No. of Orders"
|
|
||||||
},
|
|
||||||
"E9Dz18": {
|
"E9Dz18": {
|
||||||
"context": "Order summary refunds header",
|
"context": "Order summary refunds header",
|
||||||
"string": "Refunds"
|
"string": "Refunds"
|
||||||
|
@ -2609,9 +2608,6 @@
|
||||||
"context": "shipping method description",
|
"context": "shipping method description",
|
||||||
"string": "Description"
|
"string": "Description"
|
||||||
},
|
},
|
||||||
"Gr1SAu": {
|
|
||||||
"string": "Customer Name"
|
|
||||||
},
|
|
||||||
"GsBRWL": {
|
"GsBRWL": {
|
||||||
"string": "Languages"
|
"string": "Languages"
|
||||||
},
|
},
|
||||||
|
@ -3371,6 +3367,10 @@
|
||||||
"MSItJD": {
|
"MSItJD": {
|
||||||
"string": "You are about to leave the Dashboard. Do you want to continue?"
|
"string": "You are about to leave the Dashboard. Do you want to continue?"
|
||||||
},
|
},
|
||||||
|
"MTGT8E": {
|
||||||
|
"context": "column header",
|
||||||
|
"string": "No. of orders"
|
||||||
|
},
|
||||||
"MTl5o6": {
|
"MTl5o6": {
|
||||||
"context": "new discount label",
|
"context": "new discount label",
|
||||||
"string": "New discount value"
|
"string": "New discount value"
|
||||||
|
@ -6631,6 +6631,9 @@
|
||||||
"kFkPWB": {
|
"kFkPWB": {
|
||||||
"string": "Number"
|
"string": "Number"
|
||||||
},
|
},
|
||||||
|
"kFsTMN": {
|
||||||
|
"string": "Delete customers"
|
||||||
|
},
|
||||||
"kIvvax": {
|
"kIvvax": {
|
||||||
"string": "Search Products..."
|
"string": "Search Products..."
|
||||||
},
|
},
|
||||||
|
@ -6709,6 +6712,9 @@
|
||||||
"context": "balance amound missing error message",
|
"context": "balance amound missing error message",
|
||||||
"string": "Balance amount is missing"
|
"string": "Balance amount is missing"
|
||||||
},
|
},
|
||||||
|
"kdRcqU": {
|
||||||
|
"string": "Search customers..."
|
||||||
|
},
|
||||||
"kgVqk1": {
|
"kgVqk1": {
|
||||||
"string": "Category name"
|
"string": "Category name"
|
||||||
},
|
},
|
||||||
|
@ -7094,6 +7100,10 @@
|
||||||
"context": "window title",
|
"context": "window title",
|
||||||
"string": "Create customer"
|
"string": "Create customer"
|
||||||
},
|
},
|
||||||
|
"nZDQbr": {
|
||||||
|
"context": "column header",
|
||||||
|
"string": "Customer name"
|
||||||
|
},
|
||||||
"nayZY0": {
|
"nayZY0": {
|
||||||
"context": "returned event title",
|
"context": "returned event title",
|
||||||
"string": "Products were returned by"
|
"string": "Products were returned by"
|
||||||
|
@ -8392,10 +8402,6 @@
|
||||||
"context": "title",
|
"context": "title",
|
||||||
"string": "Details"
|
"string": "Details"
|
||||||
},
|
},
|
||||||
"xQK2EC": {
|
|
||||||
"context": "tab name",
|
|
||||||
"string": "All Customers"
|
|
||||||
},
|
|
||||||
"xRbqcg": {
|
"xRbqcg": {
|
||||||
"context": "option",
|
"context": "option",
|
||||||
"string": "Regular product type"
|
"string": "Regular product type"
|
||||||
|
|
|
@ -59,7 +59,7 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
|
||||||
} = useRowSelection(params);
|
} = useRowSelection(params);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
|
@ -164,7 +164,7 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
|
||||||
return (
|
return (
|
||||||
<PaginatorContext.Provider value={paginationValues}>
|
<PaginatorContext.Provider value={paginationValues}>
|
||||||
<CategoryListPage
|
<CategoryListPage
|
||||||
hasPresetsChanged={hasPresetsChange()}
|
hasPresetsChanged={hasPresetsChanged()}
|
||||||
categories={mapEdgesToItems(data?.categories)}
|
categories={mapEdgesToItems(data?.categories)}
|
||||||
currentTab={selectedPreset}
|
currentTab={selectedPreset}
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
|
|
|
@ -87,7 +87,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
const {
|
const {
|
||||||
selectedPreset,
|
selectedPreset,
|
||||||
presets,
|
presets,
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
|
@ -219,7 +219,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
onFilterChange={changeFilters}
|
onFilterChange={changeFilters}
|
||||||
selectedCollectionIds={selectedRowIds}
|
selectedCollectionIds={selectedRowIds}
|
||||||
onSelectCollectionIds={handleSetSelectedCollectionIds}
|
onSelectCollectionIds={handleSetSelectedCollectionIds}
|
||||||
hasPresetsChanged={hasPresetsChange}
|
hasPresetsChanged={hasPresetsChanged}
|
||||||
onCollectionsDelete={() =>
|
onCollectionsDelete={() =>
|
||||||
openModal("remove", {
|
openModal("remove", {
|
||||||
ids: selectedRowIds,
|
ids: selectedRowIds,
|
||||||
|
|
|
@ -14,7 +14,7 @@ const useStyles = makeStyles(
|
||||||
|
|
||||||
interface FilterTabsProps {
|
interface FilterTabsProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
currentTab: number;
|
currentTab: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FilterTabs: React.FC<FilterTabsProps> = props => {
|
export const FilterTabs: React.FC<FilterTabsProps> = props => {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import packageInfo from "../package.json";
|
import packageInfo from "../package.json";
|
||||||
import { SearchVariables } from "./hooks/makeSearch";
|
import { SearchVariables } from "./hooks/makeSearch";
|
||||||
import { ListSettings, ListViews, Pagination } from "./types";
|
import { ListSettings, ListViews, Pagination } from "./types";
|
||||||
|
@ -7,7 +6,7 @@ export const getAppDefaultUri = () => "/";
|
||||||
export const getAppMountUri = () =>
|
export const getAppMountUri = () =>
|
||||||
window?.__SALEOR_CONFIG__?.APP_MOUNT_URI || getAppDefaultUri();
|
window?.__SALEOR_CONFIG__?.APP_MOUNT_URI || getAppDefaultUri();
|
||||||
export const getApiUrl = () => window.__SALEOR_CONFIG__.API_URL;
|
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 =
|
export const IS_CLOUD_INSTANCE =
|
||||||
window.__SALEOR_CONFIG__.IS_CLOUD_INSTANCE === "true";
|
window.__SALEOR_CONFIG__.IS_CLOUD_INSTANCE === "true";
|
||||||
|
|
||||||
|
@ -80,6 +79,7 @@ export const defaultListSettings: AppListViewSettings = {
|
||||||
},
|
},
|
||||||
[ListViews.CUSTOMER_LIST]: {
|
[ListViews.CUSTOMER_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
|
columns: ["name", "email", "orders"],
|
||||||
},
|
},
|
||||||
[ListViews.DRAFT_LIST]: {
|
[ListViews.DRAFT_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
|
|
|
@ -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<CustomerListUrlSortField> {
|
|
||||||
customers: RelayToFlat<ListCustomersQuery["customers"]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CustomerList: React.FC<CustomerListProps> = 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 (
|
|
||||||
<ResponsiveTable>
|
|
||||||
<TableHead
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
selected={selected}
|
|
||||||
disabled={disabled}
|
|
||||||
items={customers}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
toolbar={toolbar}
|
|
||||||
>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === CustomerListUrlSortField.name
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
arrowPosition="right"
|
|
||||||
onClick={() => onSort(CustomerListUrlSortField.name)}
|
|
||||||
className={classes.colName}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="Gr1SAu" defaultMessage="Customer Name" />
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === CustomerListUrlSortField.email
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClick={() => onSort(CustomerListUrlSortField.email)}
|
|
||||||
className={classes.colEmail}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="97l2MO" defaultMessage="Customer Email" />
|
|
||||||
</TableCellHeader>
|
|
||||||
<RequirePermissions
|
|
||||||
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
|
|
||||||
>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === CustomerListUrlSortField.orders
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
textAlign="center"
|
|
||||||
onClick={() => onSort(CustomerListUrlSortField.orders)}
|
|
||||||
className={classes.colOrders}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="E8VDeH" defaultMessage="No. of Orders" />
|
|
||||||
</TableCellHeader>
|
|
||||||
</RequirePermissions>
|
|
||||||
</TableHead>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRowLink>
|
|
||||||
<TablePaginationWithContext
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
settings={settings}
|
|
||||||
onUpdateListSettings={onUpdateListSettings}
|
|
||||||
/>
|
|
||||||
</TableRowLink>
|
|
||||||
</TableFooter>
|
|
||||||
<TableBody>
|
|
||||||
{renderCollection(
|
|
||||||
customers,
|
|
||||||
customer => {
|
|
||||||
const isSelected = customer ? isChecked(customer.id) : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRowLink
|
|
||||||
className={!!customer ? classes.tableRow : undefined}
|
|
||||||
hover={!!customer}
|
|
||||||
key={customer ? customer.id : "skeleton"}
|
|
||||||
selected={isSelected}
|
|
||||||
href={customer && customerUrl(customer.id)}
|
|
||||||
>
|
|
||||||
<TableCell padding="checkbox">
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
disabled={disabled}
|
|
||||||
disableClickPropagation
|
|
||||||
onChange={() => toggle(customer.id)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colName}>
|
|
||||||
{getUserName(customer)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colEmail}>
|
|
||||||
{customer?.email ?? <Skeleton />}
|
|
||||||
</TableCell>
|
|
||||||
<RequirePermissions
|
|
||||||
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
|
|
||||||
>
|
|
||||||
<TableCell className={classes.colOrders}>
|
|
||||||
{customer?.orders?.totalCount ?? <Skeleton />}
|
|
||||||
</TableCell>
|
|
||||||
</RequirePermissions>
|
|
||||||
</TableRowLink>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => (
|
|
||||||
<TableRowLink>
|
|
||||||
<TableCell colSpan={numberOfColumns}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="FpIcp9"
|
|
||||||
defaultMessage="No customers found"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</ResponsiveTable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
CustomerList.displayName = "CustomerList";
|
|
||||||
export default CustomerList;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./CustomerList";
|
|
||||||
export * from "./CustomerList";
|
|
|
@ -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<CustomerListUrlSortField> {
|
||||||
|
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 (
|
||||||
|
<DatagridChangeStateContext.Provider value={datagrid}>
|
||||||
|
<Datagrid
|
||||||
|
readonly
|
||||||
|
loading={loading}
|
||||||
|
rowMarkers="checkbox"
|
||||||
|
columnSelect="single"
|
||||||
|
hasRowHover={hasRowHover}
|
||||||
|
onColumnMoved={handlers.onMove}
|
||||||
|
onColumnResize={handlers.onResize}
|
||||||
|
verticalBorder={col => 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={() => (
|
||||||
|
<ColumnPicker
|
||||||
|
staticColumns={staticColumns}
|
||||||
|
selectedColumns={selectedColumns}
|
||||||
|
onToggle={handlers.onToggle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box paddingX={6}>
|
||||||
|
<TablePaginationWithContext
|
||||||
|
component="div"
|
||||||
|
settings={settings}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DatagridChangeStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
69
src/customers/components/CustomerListDatagrid/datagrid.ts
Normal file
69
src/customers/components/CustomerListDatagrid/datagrid.ts
Normal file
|
@ -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<CustomerListUrlSortField>,
|
||||||
|
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("");
|
||||||
|
}
|
||||||
|
};
|
26
src/customers/components/CustomerListDatagrid/messages.ts
Normal file
26
src/customers/components/CustomerListDatagrid/messages.ts
Normal file
|
@ -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",
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,11 +1,11 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import {
|
import {
|
||||||
filterPageProps,
|
filterPageProps,
|
||||||
|
filterPresetsProps,
|
||||||
listActionsProps,
|
listActionsProps,
|
||||||
pageListProps,
|
pageListProps,
|
||||||
searchPageProps,
|
searchPageProps,
|
||||||
sortPageProps,
|
sortPageProps,
|
||||||
tabPageProps,
|
|
||||||
} from "@dashboard/fixtures";
|
} from "@dashboard/fixtures";
|
||||||
import { Meta, StoryObj } from "@storybook/react";
|
import { Meta, StoryObj } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -24,7 +24,7 @@ const props: CustomerListPageProps = {
|
||||||
...pageListProps.default,
|
...pageListProps.default,
|
||||||
...searchPageProps,
|
...searchPageProps,
|
||||||
...sortPageProps,
|
...sortPageProps,
|
||||||
...tabPageProps,
|
...filterPresetsProps,
|
||||||
customers: customerList,
|
customers: customerList,
|
||||||
selectedCustomerIds: ["123"],
|
selectedCustomerIds: ["123"],
|
||||||
filterOpts: {
|
filterOpts: {
|
||||||
|
@ -47,9 +47,13 @@ const props: CustomerListPageProps = {
|
||||||
...sortPageProps.sort,
|
...sortPageProps.sort,
|
||||||
sort: CustomerListUrlSortField.name,
|
sort: CustomerListUrlSortField.name,
|
||||||
},
|
},
|
||||||
|
loading: false,
|
||||||
|
hasPresetsChanged: () => false,
|
||||||
|
onSelectCustomerIds: () => undefined,
|
||||||
|
onCustomersDelete: () => undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomerListPage = props => (
|
const CustomerListPage = (props: CustomerListPageProps) => (
|
||||||
<MockedUserProvider>
|
<MockedUserProvider>
|
||||||
<CustomerListPageComponent {...props} />
|
<CustomerListPageComponent {...props} />
|
||||||
</MockedUserProvider>
|
</MockedUserProvider>
|
||||||
|
|
|
@ -6,76 +6,69 @@ import {
|
||||||
useExtensions,
|
useExtensions,
|
||||||
} from "@dashboard/apps/hooks/useExtensions";
|
} from "@dashboard/apps/hooks/useExtensions";
|
||||||
import { useUserPermissions } from "@dashboard/auth/hooks/useUserPermissions";
|
import { useUserPermissions } from "@dashboard/auth/hooks/useUserPermissions";
|
||||||
|
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import ButtonWithSelect from "@dashboard/components/ButtonWithSelect";
|
import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton";
|
||||||
import CardMenu from "@dashboard/components/CardMenu/CardMenu";
|
import { ButtonWithDropdown } from "@dashboard/components/ButtonWithDropdown";
|
||||||
import FilterBar from "@dashboard/components/FilterBar";
|
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||||
|
import { Customers } from "@dashboard/customers/types";
|
||||||
import {
|
import {
|
||||||
customerAddUrl,
|
customerAddUrl,
|
||||||
CustomerListUrlSortField,
|
CustomerListUrlSortField,
|
||||||
|
customerUrl,
|
||||||
} from "@dashboard/customers/urls";
|
} from "@dashboard/customers/urls";
|
||||||
import { ListCustomersQuery } from "@dashboard/graphql";
|
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import { sectionNames } from "@dashboard/intl";
|
import { sectionNames } from "@dashboard/intl";
|
||||||
import {
|
import {
|
||||||
FilterPageProps,
|
FilterPagePropsWithPresets,
|
||||||
ListActions,
|
|
||||||
PageListProps,
|
PageListProps,
|
||||||
RelayToFlat,
|
|
||||||
SortPage,
|
SortPage,
|
||||||
TabPageProps,
|
|
||||||
} from "@dashboard/types";
|
} from "@dashboard/types";
|
||||||
import { Card } from "@material-ui/core";
|
import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import React, { useState } from "react";
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import CustomerList from "../CustomerList/CustomerList";
|
import { CustomerListDatagrid } from "../CustomerListDatagrid/CustomerListDatagrid";
|
||||||
import {
|
import {
|
||||||
createFilterStructure,
|
createFilterStructure,
|
||||||
CustomerFilterKeys,
|
CustomerFilterKeys,
|
||||||
CustomerListFilterOpts,
|
CustomerListFilterOpts,
|
||||||
} from "./filters";
|
} from "./filters";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
settings: {
|
|
||||||
marginRight: theme.spacing(2),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "CustomerListPage" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface CustomerListPageProps
|
export interface CustomerListPageProps
|
||||||
extends PageListProps,
|
extends PageListProps,
|
||||||
ListActions,
|
FilterPagePropsWithPresets<CustomerFilterKeys, CustomerListFilterOpts>,
|
||||||
FilterPageProps<CustomerFilterKeys, CustomerListFilterOpts>,
|
SortPage<CustomerListUrlSortField> {
|
||||||
SortPage<CustomerListUrlSortField>,
|
customers: Customers | undefined;
|
||||||
TabPageProps {
|
|
||||||
customers: RelayToFlat<ListCustomersQuery["customers"]>;
|
|
||||||
selectedCustomerIds: string[];
|
selectedCustomerIds: string[];
|
||||||
|
loading: boolean;
|
||||||
|
onSelectCustomerIds: (rows: number[], clearSelection: () => void) => void;
|
||||||
|
onCustomersDelete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomerListPage: React.FC<CustomerListPageProps> = ({
|
const CustomerListPage: React.FC<CustomerListPageProps> = ({
|
||||||
currentTab,
|
selectedFilterPreset,
|
||||||
filterOpts,
|
filterOpts,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
onAll,
|
onFilterPresetsAll,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
|
onFilterPresetDelete,
|
||||||
|
onFilterPresetUpdate,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onTabChange,
|
onFilterPresetChange,
|
||||||
onTabDelete,
|
onFilterPresetPresetSave,
|
||||||
onTabSave,
|
filterPresets,
|
||||||
tabs,
|
|
||||||
selectedCustomerIds,
|
selectedCustomerIds,
|
||||||
|
hasPresetsChanged,
|
||||||
|
onCustomersDelete,
|
||||||
...customerListProps
|
...customerListProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles({});
|
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
|
||||||
const userPermissions = useUserPermissions();
|
const userPermissions = useUserPermissions();
|
||||||
const structure = createFilterStructure(intl, filterOpts, userPermissions);
|
const structure = createFilterStructure(intl, filterOpts, userPermissions);
|
||||||
|
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||||
|
|
||||||
const { CUSTOMER_OVERVIEW_CREATE, CUSTOMER_OVERVIEW_MORE_ACTIONS } =
|
const { CUSTOMER_OVERVIEW_CREATE, CUSTOMER_OVERVIEW_MORE_ACTIONS } =
|
||||||
useExtensions(extensionMountPoints.CUSTOMER_LIST);
|
useExtensions(extensionMountPoints.CUSTOMER_LIST);
|
||||||
|
@ -87,49 +80,100 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopNav title={intl.formatMessage(sectionNames.customers)}>
|
<TopNav
|
||||||
{extensionMenuItems.length > 0 && (
|
title={intl.formatMessage(sectionNames.customers)}
|
||||||
<CardMenu
|
withoutBorder
|
||||||
className={classes.settings}
|
isAlignToRight={false}
|
||||||
menuItems={extensionMenuItems}
|
>
|
||||||
/>
|
<Box
|
||||||
)}
|
__flex={1}
|
||||||
<ButtonWithSelect
|
display="flex"
|
||||||
onClick={() => navigate(customerAddUrl)}
|
justifyContent="space-between"
|
||||||
options={extensionCreateButtonItems}
|
alignItems="center"
|
||||||
data-test-id="create-customer"
|
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<Box display="flex">
|
||||||
id="QLVddq"
|
<Box marginX={5} display="flex" alignItems="center">
|
||||||
defaultMessage="Create customer"
|
<ChevronRightIcon />
|
||||||
description="button"
|
</Box>
|
||||||
/>
|
<FilterPresetsSelect
|
||||||
</ButtonWithSelect>
|
presetsChanged={hasPresetsChanged()}
|
||||||
|
onSelect={onFilterPresetChange}
|
||||||
|
onRemove={onFilterPresetDelete}
|
||||||
|
onUpdate={onFilterPresetUpdate}
|
||||||
|
savedPresets={filterPresets}
|
||||||
|
activePreset={selectedFilterPreset}
|
||||||
|
onSelectAll={onFilterPresetsAll}
|
||||||
|
onSave={onFilterPresetPresetSave}
|
||||||
|
isOpen={isFilterPresetOpen}
|
||||||
|
onOpenChange={setFilterPresetOpen}
|
||||||
|
selectAllLabel={intl.formatMessage({
|
||||||
|
id: "D95l71",
|
||||||
|
defaultMessage: "All customers",
|
||||||
|
description: "tab name",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box display="flex" alignItems="center" gap={2}>
|
||||||
|
{extensionMenuItems.length > 0 && (
|
||||||
|
<TopNav.Menu items={extensionMenuItems} />
|
||||||
|
)}
|
||||||
|
{extensionCreateButtonItems.length > 0 ? (
|
||||||
|
<ButtonWithDropdown
|
||||||
|
options={extensionCreateButtonItems}
|
||||||
|
data-test-id="create-customer"
|
||||||
|
onClick={() => navigate(customerAddUrl)}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="QLVddq"
|
||||||
|
defaultMessage="Create customer"
|
||||||
|
description="button"
|
||||||
|
/>
|
||||||
|
</ButtonWithDropdown>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
data-test-id="create-customer"
|
||||||
|
onClick={() => navigate(customerAddUrl)}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="QLVddq"
|
||||||
|
defaultMessage="Create customer"
|
||||||
|
description="button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</TopNav>
|
</TopNav>
|
||||||
<Card>
|
<Box>
|
||||||
<FilterBar
|
<ListFilters
|
||||||
allTabLabel={intl.formatMessage({
|
|
||||||
id: "xQK2EC",
|
|
||||||
defaultMessage: "All Customers",
|
|
||||||
description: "tab name",
|
|
||||||
})}
|
|
||||||
currentTab={currentTab}
|
|
||||||
filterStructure={structure}
|
filterStructure={structure}
|
||||||
initialSearch={initialSearch}
|
initialSearch={initialSearch}
|
||||||
searchPlaceholder={intl.formatMessage({
|
searchPlaceholder={intl.formatMessage({
|
||||||
id: "2mRLis",
|
id: "kdRcqU",
|
||||||
defaultMessage: "Search Customer",
|
defaultMessage: "Search customers...",
|
||||||
})}
|
})}
|
||||||
tabs={tabs}
|
|
||||||
onAll={onAll}
|
|
||||||
onFilterChange={onFilterChange}
|
onFilterChange={onFilterChange}
|
||||||
onSearchChange={onSearchChange}
|
onSearchChange={onSearchChange}
|
||||||
onTabChange={onTabChange}
|
actions={
|
||||||
onTabDelete={onTabDelete}
|
<Box display="flex" gap={4}>
|
||||||
onTabSave={onTabSave}
|
{selectedCustomerIds.length > 0 && (
|
||||||
|
<BulkDeleteButton onClick={onCustomersDelete}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Delete customers"
|
||||||
|
id="kFsTMN"
|
||||||
|
/>
|
||||||
|
</BulkDeleteButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<CustomerList {...customerListProps} />
|
<CustomerListDatagrid
|
||||||
</Card>
|
{...customerListProps}
|
||||||
|
hasRowHover={!isFilterPresetOpen}
|
||||||
|
rowAnchor={customerUrl}
|
||||||
|
onRowClick={id => navigate(customerUrl(id))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,5 +54,7 @@ export function createFilterStructure(
|
||||||
active: opts.numberOfOrders.active,
|
active: opts.numberOfOrders.active,
|
||||||
permissions: [PermissionEnum.MANAGE_ORDERS],
|
permissions: [PermissionEnum.MANAGE_ORDERS],
|
||||||
},
|
},
|
||||||
].filter(filter => hasPermissions(userPermissions, filter.permissions ?? []));
|
].filter(filter =>
|
||||||
|
hasPermissions(userPermissions ?? [], filter.permissions ?? []),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import {
|
import {
|
||||||
CustomerAddressesQuery,
|
CustomerAddressesQuery,
|
||||||
CustomerDetailsQuery,
|
CustomerDetailsQuery,
|
||||||
ListCustomersQuery,
|
|
||||||
PaymentChargeStatusEnum,
|
PaymentChargeStatusEnum,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { RelayToFlat } from "@dashboard/types";
|
|
||||||
|
import { Customers } from "./types";
|
||||||
|
|
||||||
export const customers = [
|
export const customers = [
|
||||||
{
|
{
|
||||||
|
@ -682,7 +681,7 @@ export const customers = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const customerList: RelayToFlat<ListCustomersQuery["customers"]> = [
|
export const customerList: Customers = [
|
||||||
{
|
{
|
||||||
__typename: "User",
|
__typename: "User",
|
||||||
email: "Curtis.bailey@example.com",
|
email: "Curtis.bailey@example.com",
|
||||||
|
@ -975,20 +974,20 @@ export const customer: CustomerDetailsQuery["user"] &
|
||||||
__typename: "Address",
|
__typename: "Address",
|
||||||
city: "West Feliciamouth",
|
city: "West Feliciamouth",
|
||||||
cityArea: "Montana",
|
cityArea: "Montana",
|
||||||
companyName: null,
|
companyName: "",
|
||||||
country: {
|
country: {
|
||||||
__typename: "CountryDisplay",
|
__typename: "CountryDisplay",
|
||||||
code: "JA",
|
code: "JA",
|
||||||
country: "Japan",
|
country: "Japan",
|
||||||
},
|
},
|
||||||
countryArea: null,
|
countryArea: "",
|
||||||
firstName: "Timmy",
|
firstName: "Timmy",
|
||||||
id: "33855",
|
id: "33855",
|
||||||
lastName: "Macejkovic",
|
lastName: "Macejkovic",
|
||||||
phone: "+41 460-907-9374",
|
phone: "+41 460-907-9374",
|
||||||
postalCode: "15926",
|
postalCode: "15926",
|
||||||
streetAddress1: "0238 Cremin Freeway",
|
streetAddress1: "0238 Cremin Freeway",
|
||||||
streetAddress2: null,
|
streetAddress2: "",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dateJoined: "2017-05-07T09:37:30.124154+00:00",
|
dateJoined: "2017-05-07T09:37:30.124154+00:00",
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { ListCustomersQuery } from "@dashboard/graphql";
|
||||||
|
import { RelayToFlat } from "@dashboard/types";
|
||||||
|
|
||||||
export interface AddressTypeInput {
|
export interface AddressTypeInput {
|
||||||
city: string;
|
city: string;
|
||||||
cityArea?: string;
|
cityArea?: string;
|
||||||
|
@ -29,3 +32,8 @@ export interface AddressType {
|
||||||
streetAddress1: string;
|
streetAddress1: string;
|
||||||
streetAddress2?: string;
|
streetAddress2?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Customers = RelayToFlat<
|
||||||
|
NonNullable<ListCustomersQuery["customers"]>
|
||||||
|
>;
|
||||||
|
export type Customer = Customers[number];
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import ActionDialog from "@dashboard/components/ActionDialog";
|
import ActionDialog from "@dashboard/components/ActionDialog";
|
||||||
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 { WindowTitle } from "@dashboard/components/WindowTitle";
|
import { WindowTitle } from "@dashboard/components/WindowTitle";
|
||||||
import {
|
import {
|
||||||
useBulkRemoveCustomersMutation,
|
useBulkRemoveCustomersMutation,
|
||||||
useListCustomersQuery,
|
useListCustomersQuery,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import useBulkActions from "@dashboard/hooks/useBulkActions";
|
import { useFilterPresets } from "@dashboard/hooks/useFilterPresets";
|
||||||
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";
|
||||||
|
@ -18,8 +15,8 @@ import usePaginator, {
|
||||||
createPaginationState,
|
createPaginationState,
|
||||||
PaginatorContext,
|
PaginatorContext,
|
||||||
} from "@dashboard/hooks/usePaginator";
|
} from "@dashboard/hooks/usePaginator";
|
||||||
|
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
|
||||||
import { commonMessages, sectionNames } from "@dashboard/intl";
|
import { commonMessages, sectionNames } from "@dashboard/intl";
|
||||||
import { maybe } from "@dashboard/misc";
|
|
||||||
import { ListViews } from "@dashboard/types";
|
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";
|
||||||
|
@ -27,8 +24,8 @@ 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 { DialogContentText } from "@material-ui/core";
|
import { DialogContentText } from "@material-ui/core";
|
||||||
import { DeleteIcon, IconButton } from "@saleor/macaw-ui";
|
import isEqual from "lodash/isEqual";
|
||||||
import React from "react";
|
import React, { useCallback } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import CustomerListPage from "../../components/CustomerListPage";
|
import CustomerListPage from "../../components/CustomerListPage";
|
||||||
|
@ -38,14 +35,11 @@ import {
|
||||||
CustomerListUrlQueryParams,
|
CustomerListUrlQueryParams,
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import {
|
import {
|
||||||
deleteFilterTab,
|
|
||||||
getActiveFilters,
|
|
||||||
getFilterOpts,
|
getFilterOpts,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
getFiltersCurrentTab,
|
|
||||||
getFilterTabs,
|
|
||||||
getFilterVariables,
|
getFilterVariables,
|
||||||
saveFilterTab,
|
getPresetNameToDelete,
|
||||||
|
storageUtils,
|
||||||
} from "./filters";
|
} from "./filters";
|
||||||
import { getSortQueryVariables } from "./sort";
|
import { getSortQueryVariables } from "./sort";
|
||||||
|
|
||||||
|
@ -56,16 +50,36 @@ interface CustomerListProps {
|
||||||
export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const intl = useIntl();
|
||||||
params.ids,
|
|
||||||
);
|
|
||||||
const { updateListSettings, settings } = useListSettings(
|
const { updateListSettings, settings } = useListSettings(
|
||||||
ListViews.CUSTOMER_LIST,
|
ListViews.CUSTOMER_LIST,
|
||||||
);
|
);
|
||||||
|
|
||||||
usePaginationReset(customerListUrl, params, settings.rowNumber);
|
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 paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
const queryVariables = React.useMemo(
|
const queryVariables = React.useMemo(
|
||||||
|
@ -80,18 +94,16 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
displayLoader: true,
|
displayLoader: true,
|
||||||
variables: queryVariables,
|
variables: queryVariables,
|
||||||
});
|
});
|
||||||
|
const customers = mapEdgesToItems(data?.customers);
|
||||||
const tabs = getFilterTabs();
|
|
||||||
|
|
||||||
const currentTab = getFiltersCurrentTab(params, tabs);
|
|
||||||
|
|
||||||
const [changeFilters, resetFilters, handleSearchChange] =
|
const [changeFilters, resetFilters, handleSearchChange] =
|
||||||
createFilterHandlers({
|
createFilterHandlers({
|
||||||
cleanupFn: reset,
|
cleanupFn: clearRowSelection,
|
||||||
createUrl: customerListUrl,
|
createUrl: customerListUrl,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
navigate,
|
navigate,
|
||||||
params,
|
params,
|
||||||
|
keepActiveTab: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
|
@ -99,29 +111,8 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
CustomerListUrlQueryParams
|
CustomerListUrlQueryParams
|
||||||
>(navigate, customerListUrl, params);
|
>(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({
|
const paginationValues = usePaginator({
|
||||||
pageInfo: maybe(() => data.customers.pageInfo),
|
pageInfo: data?.customers?.pageInfo,
|
||||||
paginationState,
|
paginationState,
|
||||||
queryString: params,
|
queryString: params,
|
||||||
});
|
});
|
||||||
|
@ -129,13 +120,13 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
const [bulkRemoveCustomers, bulkRemoveCustomersOpts] =
|
const [bulkRemoveCustomers, bulkRemoveCustomersOpts] =
|
||||||
useBulkRemoveCustomersMutation({
|
useBulkRemoveCustomersMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (data.customerBulkDelete.errors.length === 0) {
|
if (data.customerBulkDelete?.errors.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage(commonMessages.savedChanges),
|
text: intl.formatMessage(commonMessages.savedChanges),
|
||||||
});
|
});
|
||||||
reset();
|
|
||||||
refetch();
|
refetch();
|
||||||
|
clearRowSelection();
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -143,53 +134,67 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, customerListUrl, 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 (
|
return (
|
||||||
<PaginatorContext.Provider value={paginationValues}>
|
<PaginatorContext.Provider value={paginationValues}>
|
||||||
<WindowTitle title={intl.formatMessage(sectionNames.customers)} />
|
<WindowTitle title={intl.formatMessage(sectionNames.customers)} />
|
||||||
<CustomerListPage
|
<CustomerListPage
|
||||||
currentTab={currentTab}
|
selectedFilterPreset={selectedPreset}
|
||||||
filterOpts={getFilterOpts(params)}
|
filterOpts={getFilterOpts(params)}
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
onFilterChange={changeFilters}
|
onFilterChange={changeFilters}
|
||||||
onAll={resetFilters}
|
onFilterPresetsAll={resetFilters}
|
||||||
onTabChange={handleTabChange}
|
onFilterPresetChange={onPresetChange}
|
||||||
onTabDelete={() => openModal("delete-search")}
|
onFilterPresetDelete={(id: number) => {
|
||||||
onTabSave={() => openModal("save-search")}
|
setPresetIdToDelete(id);
|
||||||
tabs={tabs.map(tab => tab.name)}
|
openModal("delete-search");
|
||||||
customers={mapEdgesToItems(data?.customers)}
|
}}
|
||||||
|
onFilterPresetPresetSave={() => openModal("save-search")}
|
||||||
|
onFilterPresetUpdate={onPresetUpdate}
|
||||||
|
filterPresets={presets.map(preset => preset.name)}
|
||||||
|
customers={customers}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
loading={loading}
|
||||||
onUpdateListSettings={updateListSettings}
|
onUpdateListSettings={updateListSettings}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
toolbar={
|
selectedCustomerIds={selectedRowIds}
|
||||||
<IconButton
|
onSelectCustomerIds={handleSetSelectedCustomerIds}
|
||||||
variant="secondary"
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("remove", {
|
|
||||||
ids: listElements,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
selectedCustomerIds={listElements}
|
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
toggle={toggle}
|
hasPresetsChanged={hasPresetsChanged}
|
||||||
toggleAll={toggleAll}
|
onCustomersDelete={() => openModal("remove", { ids: selectedRowIds })}
|
||||||
/>
|
/>
|
||||||
<ActionDialog
|
<ActionDialog
|
||||||
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
open={params.action === "remove" && selectedRowIds?.length > 0}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
confirmButtonState={bulkRemoveCustomersOpts.status}
|
confirmButtonState={bulkRemoveCustomersOpts.status}
|
||||||
onConfirm={() =>
|
onConfirm={() =>
|
||||||
bulkRemoveCustomers({
|
bulkRemoveCustomers({
|
||||||
variables: {
|
variables: {
|
||||||
ids: params.ids,
|
ids: selectedRowIds,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -205,10 +210,8 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
id="N2SbNc"
|
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?}}"
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this customer?} other{Are you sure you want to delete {displayQuantity} customers?}}"
|
||||||
values={{
|
values={{
|
||||||
counter: maybe(() => params.ids.length),
|
counter: selectedRowIds?.length,
|
||||||
displayQuantity: (
|
displayQuantity: <strong>{selectedRowIds?.length}</strong>,
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
@ -217,14 +220,14 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
||||||
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(presets, presetIdToDelete)}
|
||||||
/>
|
/>
|
||||||
</PaginatorContext.Provider>
|
</PaginatorContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { FilterElement } from "@dashboard/components/Filter";
|
import { FilterElement } from "@dashboard/components/Filter";
|
||||||
import {
|
import {
|
||||||
CustomerFilterKeys,
|
CustomerFilterKeys,
|
||||||
CustomerListFilterOpts,
|
CustomerListFilterOpts,
|
||||||
} from "@dashboard/customers/components/CustomerListPage";
|
} from "@dashboard/customers/components/CustomerListPage";
|
||||||
import { CustomerFilterInput } from "@dashboard/graphql";
|
import { CustomerFilterInput } from "@dashboard/graphql";
|
||||||
import { maybe } from "@dashboard/misc";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createFilterTabUtils,
|
createFilterTabUtils,
|
||||||
createFilterUtils,
|
createFilterUtils,
|
||||||
|
GetFilterTabsOutput,
|
||||||
getGteLteVariables,
|
getGteLteVariables,
|
||||||
getMinMaxQueryParam,
|
getMinMaxQueryParam,
|
||||||
} from "../../../utils/filters";
|
} from "../../../utils/filters";
|
||||||
|
@ -26,29 +25,23 @@ export function getFilterOpts(
|
||||||
): CustomerListFilterOpts {
|
): CustomerListFilterOpts {
|
||||||
return {
|
return {
|
||||||
joined: {
|
joined: {
|
||||||
active: maybe(
|
active:
|
||||||
() =>
|
[params.joinedFrom, params.joinedTo].some(
|
||||||
[params.joinedFrom, params.joinedTo].some(
|
field => field !== undefined,
|
||||||
field => field !== undefined,
|
) ?? false,
|
||||||
),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
value: {
|
value: {
|
||||||
max: maybe(() => params.joinedTo, ""),
|
max: params.joinedTo ?? "",
|
||||||
min: maybe(() => params.joinedFrom, ""),
|
min: params.joinedFrom ?? "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
numberOfOrders: {
|
numberOfOrders: {
|
||||||
active: maybe(
|
active:
|
||||||
() =>
|
[params.numberOfOrdersFrom, params.numberOfOrdersTo].some(
|
||||||
[params.numberOfOrdersFrom, params.numberOfOrdersTo].some(
|
field => field !== undefined,
|
||||||
field => field !== undefined,
|
) ?? false,
|
||||||
),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
value: {
|
value: {
|
||||||
max: maybe(() => params.numberOfOrdersTo, ""),
|
max: params.numberOfOrdersTo ?? "",
|
||||||
min: maybe(() => params.numberOfOrdersFrom, ""),
|
min: params.numberOfOrdersFrom ?? "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -63,8 +56,12 @@ export function getFilterVariables(
|
||||||
lte: params.joinedTo,
|
lte: params.joinedTo,
|
||||||
}),
|
}),
|
||||||
numberOfOrders: getGteLteVariables({
|
numberOfOrders: getGteLteVariables({
|
||||||
gte: parseInt(params.numberOfOrdersFrom, 10),
|
gte: params?.numberOfOrdersFrom
|
||||||
lte: parseInt(params.numberOfOrdersTo, 10),
|
? parseInt(params.numberOfOrdersFrom, 10)
|
||||||
|
: null,
|
||||||
|
lte: params?.numberOfOrdersTo
|
||||||
|
? parseInt(params.numberOfOrdersTo, 10)
|
||||||
|
: null,
|
||||||
}),
|
}),
|
||||||
search: params.query,
|
search: params.query,
|
||||||
};
|
};
|
||||||
|
@ -92,10 +89,20 @@ export function getFilterQueryParam(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
|
export const storageUtils = createFilterTabUtils<string>(CUSTOMER_FILTERS_KEY);
|
||||||
createFilterTabUtils<CustomerListUrlFilters>(CUSTOMER_FILTERS_KEY);
|
|
||||||
|
|
||||||
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
||||||
createFilterUtils<CustomerListUrlQueryParams, CustomerListUrlFilters>(
|
createFilterUtils<CustomerListUrlQueryParams, CustomerListUrlFilters>(
|
||||||
CustomerListUrlFiltersEnum,
|
CustomerListUrlFiltersEnum,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getPresetNameToDelete = (
|
||||||
|
presets: GetFilterTabsOutput<string>,
|
||||||
|
presetIdToDelete: number | null,
|
||||||
|
): string => {
|
||||||
|
const presetIndex = presetIdToDelete ? presetIdToDelete - 1 : 0;
|
||||||
|
const preset = presets?.[presetIndex];
|
||||||
|
const tabName = preset?.name ?? "...";
|
||||||
|
|
||||||
|
return tabName;
|
||||||
|
};
|
||||||
|
|
|
@ -96,7 +96,7 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
||||||
} = useRowSelection(params);
|
} = useRowSelection(params);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
|
@ -215,7 +215,7 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
||||||
onFilterPresetsAll={resetFilters}
|
onFilterPresetsAll={resetFilters}
|
||||||
filterPresets={presets.map(preset => preset.name)}
|
filterPresets={presets.map(preset => preset.name)}
|
||||||
selectedFilterPreset={selectedPreset}
|
selectedFilterPreset={selectedPreset}
|
||||||
hasPresetsChanged={hasPresetsChange}
|
hasPresetsChanged={hasPresetsChanged}
|
||||||
onSalesDelete={() => openModal("remove")}
|
onSalesDelete={() => openModal("remove")}
|
||||||
selectedSaleIds={selectedRowIds}
|
selectedSaleIds={selectedRowIds}
|
||||||
sales={sales}
|
sales={sales}
|
||||||
|
|
|
@ -313,8 +313,8 @@ export const filterPresetsProps: FilterPresetsProps = {
|
||||||
onFilterPresetDelete: () => undefined,
|
onFilterPresetDelete: () => undefined,
|
||||||
onFilterPresetPresetSave: () => undefined,
|
onFilterPresetPresetSave: () => undefined,
|
||||||
onFilterPresetUpdate: () => undefined,
|
onFilterPresetUpdate: () => undefined,
|
||||||
filterPresets: ["Tab X"],
|
|
||||||
hasPresetsChanged: () => false,
|
hasPresetsChanged: () => false,
|
||||||
|
filterPresets: ["Tab X"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const paginatorContextValues: PaginatorContextValues = {
|
export const paginatorContextValues: PaginatorContextValues = {
|
||||||
|
|
|
@ -25,7 +25,7 @@ const GiftCardsListHeader: React.FC = () => {
|
||||||
} = useGiftCardListDialogs();
|
} = useGiftCardListDialogs();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
selectedPreset,
|
selectedPreset,
|
||||||
presets,
|
presets,
|
||||||
onPresetUpdate,
|
onPresetUpdate,
|
||||||
|
@ -57,7 +57,7 @@ const GiftCardsListHeader: React.FC = () => {
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<FilterPresetsSelect
|
<FilterPresetsSelect
|
||||||
presetsChanged={hasPresetsChange()}
|
presetsChanged={hasPresetsChanged()}
|
||||||
onSelect={onPresetChange}
|
onSelect={onPresetChange}
|
||||||
onRemove={(id: number) => {
|
onRemove={(id: number) => {
|
||||||
setPresetIdToDelete(id);
|
setPresetIdToDelete(id);
|
||||||
|
|
|
@ -18,7 +18,7 @@ export interface UseFilterPresets {
|
||||||
onPresetDelete: () => void;
|
onPresetDelete: () => void;
|
||||||
onPresetSave: (data: SaveFilterTabDialogFormData) => void;
|
onPresetSave: (data: SaveFilterTabDialogFormData) => void;
|
||||||
onPresetUpdate: (tabName: string) => void;
|
onPresetUpdate: (tabName: string) => void;
|
||||||
hasPresetsChange: () => boolean;
|
hasPresetsChanged: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFilterPresets = <
|
export const useFilterPresets = <
|
||||||
|
@ -106,7 +106,7 @@ export const useFilterPresets = <
|
||||||
onPresetChange(presets.findIndex(tab => tab.name === tabName) + 1);
|
onPresetChange(presets.findIndex(tab => tab.name === tabName) + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasPresetsChange = () => {
|
const hasPresetsChanged = () => {
|
||||||
const { parsedQs } = prepareQs(location.search);
|
const { parsedQs } = prepareQs(location.search);
|
||||||
|
|
||||||
if (!selectedPreset) {
|
if (!selectedPreset) {
|
||||||
|
@ -131,6 +131,6 @@ export const useFilterPresets = <
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
onPresetUpdate,
|
onPresetUpdate,
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,10 +17,10 @@ export const useRowSelection = (
|
||||||
const clearDatagridRowSelectionCallback = useRef<(() => void) | null>(null);
|
const clearDatagridRowSelectionCallback = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
const clearRowSelection = () => {
|
const clearRowSelection = () => {
|
||||||
setSelectedRowIds([]);
|
|
||||||
if (clearDatagridRowSelectionCallback.current) {
|
if (clearDatagridRowSelectionCallback.current) {
|
||||||
clearDatagridRowSelectionCallback.current();
|
clearDatagridRowSelectionCallback.current();
|
||||||
}
|
}
|
||||||
|
setSelectedRowIds([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setClearDatagridRowSelectionCallback = (callback: () => void) => {
|
const setClearDatagridRowSelectionCallback = (callback: () => void) => {
|
||||||
|
|
|
@ -125,7 +125,7 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
const {
|
const {
|
||||||
selectedPreset,
|
selectedPreset,
|
||||||
presets,
|
presets,
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
|
@ -221,7 +221,7 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
currencySymbol={channel?.currencyCode}
|
currencySymbol={channel?.currencyCode}
|
||||||
hasPresetsChanged={hasPresetsChange}
|
hasPresetsChanged={hasPresetsChanged}
|
||||||
onDraftOrdersDelete={() =>
|
onDraftOrdersDelete={() =>
|
||||||
openModal("remove", {
|
openModal("remove", {
|
||||||
ids: selectedRowIds,
|
ids: selectedRowIds,
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
|
@ -163,7 +163,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
||||||
onAll={resetFilters}
|
onAll={resetFilters}
|
||||||
onSettingsOpen={() => navigate(orderSettingsPath)}
|
onSettingsOpen={() => navigate(orderSettingsPath)}
|
||||||
params={params}
|
params={params}
|
||||||
hasPresetsChanged={hasPresetsChange()}
|
hasPresetsChanged={hasPresetsChanged()}
|
||||||
/>
|
/>
|
||||||
<SaveFilterTabDialog
|
<SaveFilterTabDialog
|
||||||
open={params.action === "save-search"}
|
open={params.action === "save-search"}
|
||||||
|
|
|
@ -191,7 +191,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
} = useRowSelection(params);
|
} = useRowSelection(params);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasPresetsChange,
|
hasPresetsChanged,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
onPresetDelete,
|
onPresetDelete,
|
||||||
onPresetSave,
|
onPresetSave,
|
||||||
|
@ -432,7 +432,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
}}
|
}}
|
||||||
onProductsDelete={() => openModal("delete")}
|
onProductsDelete={() => openModal("delete")}
|
||||||
onTabChange={onPresetChange}
|
onTabChange={onPresetChange}
|
||||||
hasPresetsChanged={hasPresetsChange()}
|
hasPresetsChanged={hasPresetsChanged()}
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
tabs={presets.map(tab => tab.name)}
|
tabs={presets.map(tab => tab.name)}
|
||||||
onExport={() => openModal("export")}
|
onExport={() => openModal("export")}
|
||||||
|
|
|
@ -134,7 +134,7 @@ export interface FilterPresetsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TabPageProps {
|
export interface TabPageProps {
|
||||||
currentTab: number;
|
currentTab: number | undefined;
|
||||||
tabs: string[];
|
tabs: string[];
|
||||||
onAll: () => void;
|
onAll: () => void;
|
||||||
onTabChange: (tab: number) => void;
|
onTabChange: (tab: number) => void;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"@locale/*": ["locale/*"],
|
"@locale/*": ["locale/*"],
|
||||||
"@dashboard/*": ["src/*"],
|
"@dashboard/*": ["src/*"],
|
||||||
"@test/*": ["testUtils/*"]
|
"@test/*": ["testUtils/*"]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "cypress"]
|
"exclude": ["node_modules", "cypress"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue