Order drafts list datagrid (#3765)
This commit is contained in:
parent
b4f11eff66
commit
09c9024e0d
21 changed files with 712 additions and 397 deletions
5
.changeset/weak-papayas-grab.md
Normal file
5
.changeset/weak-papayas-grab.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduce datagrid on order draft list
|
|
@ -1330,10 +1330,6 @@
|
||||||
"context": "staff member status",
|
"context": "staff member status",
|
||||||
"string": "Inactive"
|
"string": "Inactive"
|
||||||
},
|
},
|
||||||
"7a1S4K": {
|
|
||||||
"context": "tab name",
|
|
||||||
"string": "All Drafts"
|
|
||||||
},
|
|
||||||
"7dhhzL": {
|
"7dhhzL": {
|
||||||
"context": "bulk issue gift cards dialog title",
|
"context": "bulk issue gift cards dialog title",
|
||||||
"string": "Bulk Issue Gift Cards"
|
"string": "Bulk Issue Gift Cards"
|
||||||
|
@ -2929,6 +2925,9 @@
|
||||||
"context": "all authorized amount from transactions in order",
|
"context": "all authorized amount from transactions in order",
|
||||||
"string": "Authorized"
|
"string": "Authorized"
|
||||||
},
|
},
|
||||||
|
"IzECoP": {
|
||||||
|
"string": "Search draft orders..."
|
||||||
|
},
|
||||||
"IzEVek": {
|
"IzEVek": {
|
||||||
"context": "bulk disable label",
|
"context": "bulk disable label",
|
||||||
"string": "Deactivate"
|
"string": "Deactivate"
|
||||||
|
@ -3138,9 +3137,6 @@
|
||||||
"KHZlmi": {
|
"KHZlmi": {
|
||||||
"string": "Discount Type"
|
"string": "Discount Type"
|
||||||
},
|
},
|
||||||
"KIh25E": {
|
|
||||||
"string": "No draft orders found"
|
|
||||||
},
|
|
||||||
"KKQUMK": {
|
"KKQUMK": {
|
||||||
"context": "edit menu item, header",
|
"context": "edit menu item, header",
|
||||||
"string": "Edit Item"
|
"string": "Edit Item"
|
||||||
|
@ -3486,9 +3482,6 @@
|
||||||
"NGc9kE": {
|
"NGc9kE": {
|
||||||
"string": "Page type deleted"
|
"string": "Page type deleted"
|
||||||
},
|
},
|
||||||
"NJEe12": {
|
|
||||||
"string": "Search Draft"
|
|
||||||
},
|
|
||||||
"NJbzcP": {
|
"NJbzcP": {
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Cancel Orders"
|
"string": "Cancel Orders"
|
||||||
|
@ -5103,6 +5096,9 @@
|
||||||
"context": "no address is set in draft order",
|
"context": "no address is set in draft order",
|
||||||
"string": "Not set"
|
"string": "Not set"
|
||||||
},
|
},
|
||||||
|
"YJ2uRR": {
|
||||||
|
"string": "Bulk delete draft orders"
|
||||||
|
},
|
||||||
"YJ4TXc": {
|
"YJ4TXc": {
|
||||||
"context": "tab name",
|
"context": "tab name",
|
||||||
"string": "All Staff Members"
|
"string": "All Staff Members"
|
||||||
|
@ -7061,6 +7057,10 @@
|
||||||
"context": "section header",
|
"context": "section header",
|
||||||
"string": "Ongoing Installations"
|
"string": "Ongoing Installations"
|
||||||
},
|
},
|
||||||
|
"nJ0tek": {
|
||||||
|
"context": "tab name",
|
||||||
|
"string": "All draft orders"
|
||||||
|
},
|
||||||
"nKjLjT": {
|
"nKjLjT": {
|
||||||
"context": "error message",
|
"context": "error message",
|
||||||
"string": "Slug must be unique for each warehouse"
|
"string": "Slug must be unique for each warehouse"
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
||||||
return (
|
return (
|
||||||
<TopNavWrapper withoutBorder={withoutBorder}>
|
<TopNavWrapper withoutBorder={withoutBorder}>
|
||||||
{href && <TopNavLink to={href} />}
|
{href && <TopNavLink to={href} />}
|
||||||
<Box __flex={isAlignToRight ? 1 : 0}>
|
<Box __flex={isAlignToRight ? 1 : 0} __minWidth="max-content">
|
||||||
<Text variant="title" size="small">
|
<Text variant="title" size="small">
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { PaginatorContextValues } from "./hooks/usePaginator";
|
||||||
import {
|
import {
|
||||||
FetchMoreProps,
|
FetchMoreProps,
|
||||||
FilterPageProps,
|
FilterPageProps,
|
||||||
|
FilterPresetsProps,
|
||||||
ListActions,
|
ListActions,
|
||||||
SearchPageProps,
|
SearchPageProps,
|
||||||
SortPage,
|
SortPage,
|
||||||
|
@ -305,6 +306,16 @@ export const tabPageProps: TabPageProps = {
|
||||||
tabs: ["Tab X"],
|
tabs: ["Tab X"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const filterPresetsProps: FilterPresetsProps = {
|
||||||
|
selectedFilterPreset: 0,
|
||||||
|
onFilterPresetsAll: () => undefined,
|
||||||
|
onFilterPresetChange: () => undefined,
|
||||||
|
onFilterPresetDelete: () => undefined,
|
||||||
|
onFilterPresetPresetSave: () => undefined,
|
||||||
|
onFilterPresetUpdate: () => undefined,
|
||||||
|
filterPresets: ["Tab X"],
|
||||||
|
};
|
||||||
|
|
||||||
export const paginatorContextValues: PaginatorContextValues = {
|
export const paginatorContextValues: PaginatorContextValues = {
|
||||||
endCursor: "",
|
endCursor: "",
|
||||||
startCursor: "",
|
startCursor: "",
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import Checkbox from "@dashboard/components/Checkbox";
|
|
||||||
import { DateTime } from "@dashboard/components/Date";
|
|
||||||
import Money from "@dashboard/components/Money";
|
|
||||||
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 { OrderDraftListQuery } from "@dashboard/graphql";
|
|
||||||
import {
|
|
||||||
maybe,
|
|
||||||
renderCollection,
|
|
||||||
transformOrderStatus,
|
|
||||||
transformPaymentStatus,
|
|
||||||
} from "@dashboard/misc";
|
|
||||||
import { OrderDraftListUrlSortField, orderUrl } from "@dashboard/orders/urls";
|
|
||||||
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, useIntl } from "react-intl";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
[theme.breakpoints.up("lg")]: {
|
|
||||||
colCustomer: {
|
|
||||||
width: 300,
|
|
||||||
},
|
|
||||||
colDate: {
|
|
||||||
width: 300,
|
|
||||||
},
|
|
||||||
colNumber: {
|
|
||||||
width: 160,
|
|
||||||
},
|
|
||||||
colTotal: {},
|
|
||||||
},
|
|
||||||
colCustomer: {},
|
|
||||||
colDate: {},
|
|
||||||
colNumber: {
|
|
||||||
paddingLeft: 0,
|
|
||||||
},
|
|
||||||
colTotal: {
|
|
||||||
textAlign: "right",
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
cursor: "pointer",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "OrderDraftList" },
|
|
||||||
);
|
|
||||||
|
|
||||||
interface OrderDraftListProps
|
|
||||||
extends ListProps,
|
|
||||||
ListActions,
|
|
||||||
SortPage<OrderDraftListUrlSortField> {
|
|
||||||
orders: RelayToFlat<OrderDraftListQuery["draftOrders"]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OrderDraftList: React.FC<OrderDraftListProps> = props => {
|
|
||||||
const {
|
|
||||||
disabled,
|
|
||||||
settings,
|
|
||||||
orders,
|
|
||||||
onUpdateListSettings,
|
|
||||||
onSort,
|
|
||||||
isChecked,
|
|
||||||
selected,
|
|
||||||
sort,
|
|
||||||
toggle,
|
|
||||||
toggleAll,
|
|
||||||
toolbar,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const classes = useStyles(props);
|
|
||||||
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const orderDraftList = orders
|
|
||||||
? orders.map(order => ({
|
|
||||||
...order,
|
|
||||||
paymentStatus: transformPaymentStatus(order.paymentStatus, intl),
|
|
||||||
status: transformOrderStatus(order.status, intl),
|
|
||||||
}))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const numberOfColumns = orderDraftList?.length === 0 ? 4 : 5;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ResponsiveTable>
|
|
||||||
<TableHead
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
selected={selected}
|
|
||||||
disabled={disabled}
|
|
||||||
items={orders}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
toolbar={toolbar}
|
|
||||||
>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === OrderDraftListUrlSortField.number
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
arrowPosition="right"
|
|
||||||
onClick={() => onSort(OrderDraftListUrlSortField.number)}
|
|
||||||
className={classes.colNumber}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="kFkPWB" defaultMessage="Number" />
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === OrderDraftListUrlSortField.date
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClick={() => onSort(OrderDraftListUrlSortField.date)}
|
|
||||||
className={classes.colDate}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="mCP0UD"
|
|
||||||
defaultMessage="Date"
|
|
||||||
description="order draft creation date"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === OrderDraftListUrlSortField.customer
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClick={() => onSort(OrderDraftListUrlSortField.customer)}
|
|
||||||
className={classes.colCustomer}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="hkENym" defaultMessage="Customer" />
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader textAlign="right" className={classes.colTotal}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="1Uj0Wd"
|
|
||||||
defaultMessage="Total"
|
|
||||||
description="order draft total price"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
</TableHead>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRowLink>
|
|
||||||
<TablePaginationWithContext
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
settings={settings}
|
|
||||||
onUpdateListSettings={onUpdateListSettings}
|
|
||||||
/>
|
|
||||||
</TableRowLink>
|
|
||||||
</TableFooter>
|
|
||||||
<TableBody>
|
|
||||||
{renderCollection(
|
|
||||||
orderDraftList,
|
|
||||||
order => {
|
|
||||||
const isSelected = order ? isChecked(order.id) : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRowLink
|
|
||||||
data-test-id="draft-order-table-row"
|
|
||||||
hover={!!order}
|
|
||||||
className={!!order ? classes.link : undefined}
|
|
||||||
href={order && orderUrl(order.id)}
|
|
||||||
key={order ? order.id : "skeleton"}
|
|
||||||
selected={isSelected}
|
|
||||||
>
|
|
||||||
<TableCell padding="checkbox">
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
disabled={disabled}
|
|
||||||
disableClickPropagation
|
|
||||||
onChange={() => toggle(order.id)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colNumber}>
|
|
||||||
{maybe(() => order.number) ? (
|
|
||||||
"#" + order.number
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colDate}>
|
|
||||||
{maybe(() => order.created) ? (
|
|
||||||
<DateTime date={order.created} plain />
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colCustomer}>
|
|
||||||
{maybe(() => order.billingAddress) ? (
|
|
||||||
<>
|
|
||||||
{order.billingAddress.firstName}
|
|
||||||
|
|
||||||
{order.billingAddress.lastName}
|
|
||||||
</>
|
|
||||||
) : maybe(() => order.userEmail) !== undefined ? (
|
|
||||||
order.userEmail
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.colTotal} align="right">
|
|
||||||
{maybe(() => order.total.gross) ? (
|
|
||||||
<Money money={order.total.gross} />
|
|
||||||
) : (
|
|
||||||
<Skeleton />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => (
|
|
||||||
<TableRowLink>
|
|
||||||
<TableCell colSpan={numberOfColumns}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="KIh25E"
|
|
||||||
defaultMessage="No draft orders found"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</ResponsiveTable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
OrderDraftList.displayName = "OrderDraftList";
|
|
||||||
export default OrderDraftList;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./OrderDraftList";
|
|
||||||
export * from "./OrderDraftList";
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
// @ts-strict-ignore
|
||||||
|
import ColumnPicker from "@dashboard/components/ColumnPicker/ColumnPicker";
|
||||||
|
import Datagrid from "@dashboard/components/Datagrid/Datagrid";
|
||||||
|
import { useColumnsDefault } from "@dashboard/components/Datagrid/hooks/useColumnsDefault";
|
||||||
|
import {
|
||||||
|
DatagridChangeStateContext,
|
||||||
|
useDatagridChangeState,
|
||||||
|
} from "@dashboard/components/Datagrid/hooks/useDatagridChange";
|
||||||
|
import { TablePaginationWithContext } from "@dashboard/components/TablePagination";
|
||||||
|
import { OrderDraftListQuery } from "@dashboard/graphql";
|
||||||
|
import useLocale from "@dashboard/hooks/useLocale";
|
||||||
|
import { OrderDraftListUrlSortField, orderUrl } from "@dashboard/orders/urls";
|
||||||
|
import { ListProps, RelayToFlat, 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, getColumns } from "./datagrid";
|
||||||
|
import { messages } from "./messages";
|
||||||
|
import { canBeSorted } from "./utils";
|
||||||
|
|
||||||
|
interface OrderDraftListDatagridProps
|
||||||
|
extends ListProps,
|
||||||
|
SortPage<OrderDraftListUrlSortField> {
|
||||||
|
orders: RelayToFlat<OrderDraftListQuery["draftOrders"]>;
|
||||||
|
hasRowHover?: boolean;
|
||||||
|
onRowClick?: (id: string) => void;
|
||||||
|
onSelectOrderDraftIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderDraftListDatagrid = ({
|
||||||
|
disabled,
|
||||||
|
orders,
|
||||||
|
sort,
|
||||||
|
onSort,
|
||||||
|
hasRowHover,
|
||||||
|
onRowClick,
|
||||||
|
settings,
|
||||||
|
onUpdateListSettings,
|
||||||
|
onSelectOrderDraftIds,
|
||||||
|
}: OrderDraftListDatagridProps) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { locale } = useLocale();
|
||||||
|
const datagridState = useDatagridChangeState();
|
||||||
|
|
||||||
|
const availableColumns = useMemo(() => getColumns(intl, sort), [intl, sort]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
availableColumnsChoices,
|
||||||
|
columnChoices,
|
||||||
|
defaultColumns,
|
||||||
|
onColumnMoved,
|
||||||
|
onColumnResize,
|
||||||
|
onColumnsChange,
|
||||||
|
picker,
|
||||||
|
} = useColumnsDefault(availableColumns);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const getCellContent = useCallback(
|
||||||
|
createGetCellContent({ orders, columns, locale }),
|
||||||
|
[columns, locale, orders],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleHeaderClick = useCallback(
|
||||||
|
(col: number) => {
|
||||||
|
const columnName = columns[col].id as OrderDraftListUrlSortField;
|
||||||
|
if (canBeSorted(columnName)) {
|
||||||
|
onSort(columnName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[columns, onSort],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRowClick = useCallback(
|
||||||
|
([_, row]: Item) => {
|
||||||
|
if (!onRowClick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowData = orders[row];
|
||||||
|
onRowClick(rowData.id);
|
||||||
|
},
|
||||||
|
[onRowClick, orders],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRowAnchor = useCallback(
|
||||||
|
([, row]: Item) => {
|
||||||
|
const rowData = orders[row];
|
||||||
|
return orderUrl(rowData.id);
|
||||||
|
},
|
||||||
|
[orders],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatagridChangeStateContext.Provider value={datagridState}>
|
||||||
|
<Datagrid
|
||||||
|
readonly
|
||||||
|
rowMarkers="checkbox"
|
||||||
|
columnSelect="single"
|
||||||
|
freezeColumns={1}
|
||||||
|
hasRowHover={hasRowHover}
|
||||||
|
loading={disabled}
|
||||||
|
availableColumns={columns}
|
||||||
|
verticalBorder={col => col > 0}
|
||||||
|
getCellContent={getCellContent}
|
||||||
|
getCellError={() => false}
|
||||||
|
menuItems={() => []}
|
||||||
|
emptyText={intl.formatMessage(messages.emptyText)}
|
||||||
|
rows={orders?.length ?? 0}
|
||||||
|
selectionActions={() => null}
|
||||||
|
onRowSelectionChange={onSelectOrderDraftIds}
|
||||||
|
onColumnMoved={onColumnMoved}
|
||||||
|
onColumnResize={onColumnResize}
|
||||||
|
onHeaderClicked={handleHeaderClick}
|
||||||
|
onRowClick={handleRowClick}
|
||||||
|
rowAnchor={handleRowAnchor}
|
||||||
|
renderColumnPicker={defaultProps => (
|
||||||
|
<ColumnPicker
|
||||||
|
{...defaultProps}
|
||||||
|
IconButtonProps={{
|
||||||
|
...defaultProps.IconButtonProps,
|
||||||
|
disabled: orders.length === 0,
|
||||||
|
}}
|
||||||
|
availableColumns={availableColumnsChoices}
|
||||||
|
initialColumns={columnChoices}
|
||||||
|
defaultColumns={defaultColumns}
|
||||||
|
onSave={onColumnsChange}
|
||||||
|
hasMore={false}
|
||||||
|
loading={false}
|
||||||
|
onFetchMore={() => undefined}
|
||||||
|
onQueryChange={picker.setQuery}
|
||||||
|
query={picker.query}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box paddingX={6}>
|
||||||
|
<TablePaginationWithContext
|
||||||
|
component="div"
|
||||||
|
colSpan={1}
|
||||||
|
settings={settings}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DatagridChangeStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { OrderDraftListQuery } from "@dashboard/graphql";
|
||||||
|
import { RelayToFlat } from "@dashboard/types";
|
||||||
|
|
||||||
|
import { getCustomerName } from "./datagrid";
|
||||||
|
|
||||||
|
describe("getCustomerName", () => {
|
||||||
|
it("should return billing address first name and last name when exists", () => {
|
||||||
|
// Arrange
|
||||||
|
const data = {
|
||||||
|
billingAddress: {
|
||||||
|
firstName: "John",
|
||||||
|
lastName: "Doe",
|
||||||
|
},
|
||||||
|
} as RelayToFlat<NonNullable<OrderDraftListQuery["draftOrders"]>>[number];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getCustomerName(data);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual("John Doe");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return user email when exists", () => {
|
||||||
|
// Arrange
|
||||||
|
const data = {
|
||||||
|
billingAddress: {
|
||||||
|
city: "New York",
|
||||||
|
},
|
||||||
|
userEmail: "john@doe.com",
|
||||||
|
} as RelayToFlat<NonNullable<OrderDraftListQuery["draftOrders"]>>[number];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getCustomerName(data);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual("john@doe.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return - when no user email and billing address", () => {
|
||||||
|
// Arrange
|
||||||
|
const data = {} as RelayToFlat<
|
||||||
|
NonNullable<OrderDraftListQuery["draftOrders"]>
|
||||||
|
>[number];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getCustomerName(data);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual("-");
|
||||||
|
});
|
||||||
|
});
|
94
src/orders/components/OrderDraftListDatagrid/datagrid.ts
Normal file
94
src/orders/components/OrderDraftListDatagrid/datagrid.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// @ts-strict-ignore
|
||||||
|
import {
|
||||||
|
moneyCell,
|
||||||
|
readonlyTextCell,
|
||||||
|
} from "@dashboard/components/Datagrid/customCells/cells";
|
||||||
|
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
||||||
|
import { Locale } from "@dashboard/components/Locale";
|
||||||
|
import { OrderDraftListQuery } from "@dashboard/graphql";
|
||||||
|
import { RelayToFlat, Sort } from "@dashboard/types";
|
||||||
|
import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon";
|
||||||
|
import { GridCell, Item } from "@glideapps/glide-data-grid";
|
||||||
|
import moment from "moment";
|
||||||
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
|
import { columnsMessages } from "./messages";
|
||||||
|
|
||||||
|
export const getColumns = (intl: IntlShape, sort: Sort): AvailableColumn[] => [
|
||||||
|
{
|
||||||
|
id: "number",
|
||||||
|
title: intl.formatMessage(columnsMessages.number),
|
||||||
|
width: 100,
|
||||||
|
icon: getColumnSortDirectionIcon(sort, "number"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "date",
|
||||||
|
title: intl.formatMessage(columnsMessages.date),
|
||||||
|
width: 200,
|
||||||
|
icon: getColumnSortDirectionIcon(sort, "date"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "customer",
|
||||||
|
title: intl.formatMessage(columnsMessages.customer),
|
||||||
|
width: 200,
|
||||||
|
icon: getColumnSortDirectionIcon(sort, "customer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "total",
|
||||||
|
title: intl.formatMessage(columnsMessages.total),
|
||||||
|
width: 200,
|
||||||
|
icon: getColumnSortDirectionIcon(sort, "total"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const createGetCellContent =
|
||||||
|
({
|
||||||
|
orders,
|
||||||
|
locale,
|
||||||
|
columns,
|
||||||
|
}: {
|
||||||
|
orders: RelayToFlat<OrderDraftListQuery["draftOrders"]>;
|
||||||
|
columns: AvailableColumn[];
|
||||||
|
locale: Locale;
|
||||||
|
}) =>
|
||||||
|
([column, row]: Item): GridCell => {
|
||||||
|
const rowData = orders[row];
|
||||||
|
const columnId = columns[column]?.id;
|
||||||
|
|
||||||
|
if (!columnId) {
|
||||||
|
return readonlyTextCell("");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (columnId) {
|
||||||
|
case "number":
|
||||||
|
return readonlyTextCell(`#${rowData.number}`);
|
||||||
|
case "date":
|
||||||
|
return readonlyTextCell(
|
||||||
|
moment(rowData.created).locale(locale).format("lll"),
|
||||||
|
);
|
||||||
|
case "customer":
|
||||||
|
return readonlyTextCell(getCustomerName(rowData));
|
||||||
|
case "total":
|
||||||
|
return moneyCell(
|
||||||
|
rowData.total?.gross?.amount ?? 0,
|
||||||
|
rowData.total?.gross?.currency ?? "",
|
||||||
|
{
|
||||||
|
readonly: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getCustomerName(
|
||||||
|
rowData: RelayToFlat<OrderDraftListQuery["draftOrders"]>[number],
|
||||||
|
) {
|
||||||
|
if (rowData?.billingAddress?.firstName && rowData?.billingAddress?.lastName) {
|
||||||
|
return `${rowData.billingAddress.firstName} ${rowData.billingAddress.lastName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowData.userEmail) {
|
||||||
|
return rowData.userEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "-";
|
||||||
|
}
|
1
src/orders/components/OrderDraftListDatagrid/index.ts
Normal file
1
src/orders/components/OrderDraftListDatagrid/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./OrderDraftListDatagrid";
|
29
src/orders/components/OrderDraftListDatagrid/messages.ts
Normal file
29
src/orders/components/OrderDraftListDatagrid/messages.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
emptyText: {
|
||||||
|
defaultMessage: "No orders found",
|
||||||
|
id: "RlfqSV",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const columnsMessages = defineMessages({
|
||||||
|
number: {
|
||||||
|
defaultMessage: "Number",
|
||||||
|
id: "kFkPWB",
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
id: "mCP0UD",
|
||||||
|
defaultMessage: "Date",
|
||||||
|
description: "order draft creation date",
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
id: "hkENym",
|
||||||
|
defaultMessage: "Customer",
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
id: "1Uj0Wd",
|
||||||
|
defaultMessage: "Total",
|
||||||
|
description: "order draft total price",
|
||||||
|
},
|
||||||
|
});
|
12
src/orders/components/OrderDraftListDatagrid/utils.ts
Normal file
12
src/orders/components/OrderDraftListDatagrid/utils.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { OrderDraftListUrlSortField } from "@dashboard/orders/urls";
|
||||||
|
|
||||||
|
export function canBeSorted(sort: OrderDraftListUrlSortField) {
|
||||||
|
switch (sort) {
|
||||||
|
case OrderDraftListUrlSortField.number:
|
||||||
|
case OrderDraftListUrlSortField.date:
|
||||||
|
case OrderDraftListUrlSortField.customer:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Button, Tooltip, TrashBinIcon } from "@saleor/macaw-ui/next";
|
||||||
|
import React, { forwardRef, ReactNode, useState } from "react";
|
||||||
|
|
||||||
|
interface OrderDraftListDeleteButtonProps {
|
||||||
|
onClick: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderDraftListDeleteButton = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
OrderDraftListDeleteButtonProps
|
||||||
|
>(({ onClick, children }, ref) => {
|
||||||
|
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip open={isTooltipOpen}>
|
||||||
|
<Tooltip.Trigger>
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
onMouseOver={() => {
|
||||||
|
setIsTooltipOpen(true);
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
setIsTooltipOpen(false);
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<TrashBinIcon />}
|
||||||
|
variant="secondary"
|
||||||
|
data-test-id="delete-categories-button"
|
||||||
|
/>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content side="bottom">
|
||||||
|
<Tooltip.Arrow />
|
||||||
|
{children}
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./OrderDraftListDeleteButton";
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { TopNav } from "@dashboard/components/AppLayout";
|
||||||
|
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
||||||
|
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||||
|
import { RefreshLimitsQuery } from "@dashboard/graphql";
|
||||||
|
import { sectionNames } from "@dashboard/intl";
|
||||||
|
import { FilterPresetsProps } from "@dashboard/types";
|
||||||
|
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
||||||
|
import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
export interface OrderDraftListHeaderProps
|
||||||
|
extends Omit<FilterPresetsProps, "onTabDelete"> {
|
||||||
|
limits: RefreshLimitsQuery["shop"]["limits"];
|
||||||
|
hasPresetsChanged: boolean;
|
||||||
|
isFilterPresetOpen: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
onAdd: () => void;
|
||||||
|
setFilterPresetOpen: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderDraftListHeader = ({
|
||||||
|
hasPresetsChanged,
|
||||||
|
onFilterPresetChange,
|
||||||
|
onFilterPresetDelete,
|
||||||
|
onFilterPresetUpdate,
|
||||||
|
onFilterPresetPresetSave,
|
||||||
|
filterPresets,
|
||||||
|
selectedFilterPreset,
|
||||||
|
onFilterPresetsAll,
|
||||||
|
isFilterPresetOpen,
|
||||||
|
setFilterPresetOpen,
|
||||||
|
disabled,
|
||||||
|
limits,
|
||||||
|
onAdd,
|
||||||
|
}: OrderDraftListHeaderProps) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const limitsReached = isLimitReached(limits, "orders");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TopNav
|
||||||
|
title={intl.formatMessage(sectionNames.draftOrders)}
|
||||||
|
withoutBorder
|
||||||
|
isAlignToRight={false}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
__flex={1}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Box display="flex">
|
||||||
|
<Box marginX={3} display="flex" alignItems="center">
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FilterPresetsSelect
|
||||||
|
presetsChanged={hasPresetsChanged}
|
||||||
|
onSelect={onFilterPresetChange}
|
||||||
|
onRemove={onFilterPresetDelete}
|
||||||
|
onUpdate={onFilterPresetUpdate}
|
||||||
|
savedPresets={filterPresets}
|
||||||
|
activePreset={selectedFilterPreset}
|
||||||
|
onSelectAll={onFilterPresetsAll}
|
||||||
|
onSave={onFilterPresetPresetSave}
|
||||||
|
isOpen={isFilterPresetOpen}
|
||||||
|
onOpenChange={setFilterPresetOpen}
|
||||||
|
selectAllLabel={intl.formatMessage({
|
||||||
|
id: "nJ0tek",
|
||||||
|
defaultMessage: "All draft orders",
|
||||||
|
description: "tab name",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box display="flex" alignItems="center" gap={2}>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
disabled={disabled || limitsReached}
|
||||||
|
onClick={onAdd}
|
||||||
|
data-test-id="create-draft-order-button"
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="LshEVn"
|
||||||
|
defaultMessage="Create order"
|
||||||
|
description="button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{hasLimits(limits, "orders") && (
|
||||||
|
<LimitsInfo
|
||||||
|
text={intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "w2eTzO",
|
||||||
|
defaultMessage: "{count}/{max} orders",
|
||||||
|
description: "placed orders counter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: limits.currentUsage.orders,
|
||||||
|
max: limits.allowedUsage.orders,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</TopNav>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import {
|
import {
|
||||||
filterPageProps,
|
filterPageProps,
|
||||||
|
filterPresetsProps,
|
||||||
limits,
|
limits,
|
||||||
limitsReached,
|
limitsReached,
|
||||||
listActionsProps,
|
listActionsProps,
|
||||||
|
@ -22,6 +23,7 @@ const props: OrderDraftListPageProps = {
|
||||||
...listActionsProps,
|
...listActionsProps,
|
||||||
...pageListProps.default,
|
...pageListProps.default,
|
||||||
...searchPageProps,
|
...searchPageProps,
|
||||||
|
...filterPresetsProps,
|
||||||
...sortPageProps,
|
...sortPageProps,
|
||||||
...tabPageProps,
|
...tabPageProps,
|
||||||
...filterPageProps,
|
...filterPageProps,
|
||||||
|
@ -45,6 +47,10 @@ const props: OrderDraftListPageProps = {
|
||||||
...sortPageProps.sort,
|
...sortPageProps.sort,
|
||||||
sort: OrderDraftListUrlSortField.number,
|
sort: OrderDraftListUrlSortField.number,
|
||||||
},
|
},
|
||||||
|
onDraftOrdersDelete: () => undefined,
|
||||||
|
onSelectOrderDraftIds: () => undefined,
|
||||||
|
selectedOrderDraftIds: [],
|
||||||
|
hasPresetsChanged: () => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof OrderDraftListPage> = {
|
const meta: Meta<typeof OrderDraftListPage> = {
|
||||||
|
|
|
@ -1,25 +1,22 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
|
||||||
import { Button } from "@dashboard/components/Button";
|
|
||||||
import FilterBar from "@dashboard/components/FilterBar";
|
|
||||||
import { OrderDraftListQuery, RefreshLimitsQuery } from "@dashboard/graphql";
|
import { OrderDraftListQuery, RefreshLimitsQuery } from "@dashboard/graphql";
|
||||||
import { sectionNames } from "@dashboard/intl";
|
|
||||||
import { OrderDraftListUrlSortField } from "@dashboard/orders/urls";
|
import { OrderDraftListUrlSortField } from "@dashboard/orders/urls";
|
||||||
import {
|
import {
|
||||||
FilterPageProps,
|
FilterPagePropsWithPresets,
|
||||||
ListActions,
|
|
||||||
PageListProps,
|
PageListProps,
|
||||||
RelayToFlat,
|
RelayToFlat,
|
||||||
SortPage,
|
SortPage,
|
||||||
TabPageProps,
|
|
||||||
} from "@dashboard/types";
|
} from "@dashboard/types";
|
||||||
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
import { isLimitReached } from "@dashboard/utils/limits";
|
||||||
import { Card } from "@material-ui/core";
|
import { Card } from "@material-ui/core";
|
||||||
import React from "react";
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import React, { useState } from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import OrderDraftList from "../OrderDraftList";
|
import { OrderDraftListDatagrid } from "../OrderDraftListDatagrid";
|
||||||
|
import { OrderDraftListDeleteButton } from "../OrderDraftListDeleteButton";
|
||||||
|
import { OrderDraftListHeader } from "../OrderDraftListHeader/OrderDraftListHeader";
|
||||||
import OrderLimitReached from "../OrderLimitReached";
|
import OrderLimitReached from "../OrderLimitReached";
|
||||||
import {
|
import {
|
||||||
createFilterStructure,
|
createFilterStructure,
|
||||||
|
@ -29,90 +26,103 @@ import {
|
||||||
|
|
||||||
export interface OrderDraftListPageProps
|
export interface OrderDraftListPageProps
|
||||||
extends PageListProps,
|
extends PageListProps,
|
||||||
ListActions,
|
FilterPagePropsWithPresets<OrderDraftFilterKeys, OrderDraftListFilterOpts>,
|
||||||
FilterPageProps<OrderDraftFilterKeys, OrderDraftListFilterOpts>,
|
SortPage<OrderDraftListUrlSortField> {
|
||||||
SortPage<OrderDraftListUrlSortField>,
|
|
||||||
TabPageProps {
|
|
||||||
limits: RefreshLimitsQuery["shop"]["limits"];
|
limits: RefreshLimitsQuery["shop"]["limits"];
|
||||||
orders: RelayToFlat<OrderDraftListQuery["draftOrders"]>;
|
orders: RelayToFlat<OrderDraftListQuery["draftOrders"]>;
|
||||||
|
selectedOrderDraftIds: string[];
|
||||||
|
hasPresetsChanged: () => boolean;
|
||||||
onAdd: () => void;
|
onAdd: () => void;
|
||||||
|
onDraftOrdersDelete: () => void;
|
||||||
|
onSelectOrderDraftIds: (ids: number[], clearSelection: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderDraftListPage: React.FC<OrderDraftListPageProps> = ({
|
const OrderDraftListPage: React.FC<OrderDraftListPageProps> = ({
|
||||||
currentTab,
|
selectedFilterPreset,
|
||||||
disabled,
|
disabled,
|
||||||
filterOpts,
|
filterOpts,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
limits,
|
limits,
|
||||||
onAdd,
|
onAdd,
|
||||||
onAll,
|
onFilterPresetsAll,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onTabChange,
|
onFilterPresetChange,
|
||||||
onTabDelete,
|
onFilterPresetDelete,
|
||||||
onTabSave,
|
onFilterPresetUpdate,
|
||||||
tabs,
|
onFilterPresetPresetSave,
|
||||||
|
filterPresets,
|
||||||
|
hasPresetsChanged,
|
||||||
|
onDraftOrdersDelete,
|
||||||
|
onFilterAttributeFocus,
|
||||||
|
currencySymbol,
|
||||||
|
selectedOrderDraftIds,
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const structure = createFilterStructure(intl, filterOpts);
|
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||||
|
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||||
const limitsReached = isLimitReached(limits, "orders");
|
const limitsReached = isLimitReached(limits, "orders");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopNav title={intl.formatMessage(sectionNames.draftOrders)}>
|
<OrderDraftListHeader
|
||||||
<Button
|
disabled={disabled}
|
||||||
variant="primary"
|
selectedFilterPreset={selectedFilterPreset}
|
||||||
disabled={disabled || limitsReached}
|
hasPresetsChanged={hasPresetsChanged()}
|
||||||
onClick={onAdd}
|
isFilterPresetOpen={isFilterPresetOpen}
|
||||||
data-test-id="create-draft-order-button"
|
setFilterPresetOpen={setFilterPresetOpen}
|
||||||
>
|
limits={limits}
|
||||||
<FormattedMessage
|
onAdd={onAdd}
|
||||||
id="LshEVn"
|
onFilterPresetsAll={onFilterPresetsAll}
|
||||||
defaultMessage="Create order"
|
onFilterPresetDelete={onFilterPresetDelete}
|
||||||
description="button"
|
onFilterPresetChange={onFilterPresetChange}
|
||||||
/>
|
onFilterPresetPresetSave={onFilterPresetPresetSave}
|
||||||
</Button>
|
onFilterPresetUpdate={onFilterPresetUpdate}
|
||||||
{hasLimits(limits, "orders") && (
|
filterPresets={filterPresets}
|
||||||
<LimitsInfo
|
/>
|
||||||
text={intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: "w2eTzO",
|
|
||||||
defaultMessage: "{count}/{max} orders",
|
|
||||||
description: "placed orders counter",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
count: limits.currentUsage.orders,
|
|
||||||
max: limits.allowedUsage.orders,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TopNav>
|
|
||||||
{limitsReached && <OrderLimitReached />}
|
{limitsReached && <OrderLimitReached />}
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<FilterBar
|
<Box
|
||||||
allTabLabel={intl.formatMessage({
|
display="flex"
|
||||||
id: "7a1S4K",
|
flexDirection="column"
|
||||||
defaultMessage: "All Drafts",
|
width="100%"
|
||||||
description: "tab name",
|
alignItems="stretch"
|
||||||
})}
|
justifyContent="space-between"
|
||||||
currentTab={currentTab}
|
>
|
||||||
filterStructure={structure}
|
<ListFilters
|
||||||
initialSearch={initialSearch}
|
currencySymbol={currencySymbol}
|
||||||
searchPlaceholder={intl.formatMessage({
|
initialSearch={initialSearch}
|
||||||
id: "NJEe12",
|
onFilterChange={onFilterChange}
|
||||||
defaultMessage: "Search Draft",
|
onFilterAttributeFocus={onFilterAttributeFocus}
|
||||||
})}
|
onSearchChange={onSearchChange}
|
||||||
tabs={tabs}
|
filterStructure={filterStructure}
|
||||||
onAll={onAll}
|
searchPlaceholder={intl.formatMessage({
|
||||||
onFilterChange={onFilterChange}
|
id: "IzECoP",
|
||||||
onSearchChange={onSearchChange}
|
defaultMessage: "Search draft orders...",
|
||||||
onTabChange={onTabChange}
|
})}
|
||||||
onTabDelete={onTabDelete}
|
actions={
|
||||||
onTabSave={onTabSave}
|
<Box display="flex" gap={4}>
|
||||||
|
{selectedOrderDraftIds.length > 0 && (
|
||||||
|
<OrderDraftListDeleteButton onClick={onDraftOrdersDelete}>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "YJ2uRR",
|
||||||
|
defaultMessage: "Bulk delete draft orders",
|
||||||
|
})}
|
||||||
|
</OrderDraftListDeleteButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<OrderDraftListDatagrid
|
||||||
|
disabled={disabled}
|
||||||
|
hasRowHover={!isFilterPresetOpen}
|
||||||
|
{...listProps}
|
||||||
/>
|
/>
|
||||||
<OrderDraftList disabled={disabled} {...listProps} />
|
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,16 +3,14 @@ import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDia
|
||||||
import ActionDialog from "@dashboard/components/ActionDialog";
|
import ActionDialog from "@dashboard/components/ActionDialog";
|
||||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||||
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 { useShopLimitsQuery } from "@dashboard/components/Shop/queries";
|
import { useShopLimitsQuery } from "@dashboard/components/Shop/queries";
|
||||||
import {
|
import {
|
||||||
useOrderDraftBulkCancelMutation,
|
useOrderDraftBulkCancelMutation,
|
||||||
useOrderDraftCreateMutation,
|
useOrderDraftCreateMutation,
|
||||||
useOrderDraftListQuery,
|
useOrderDraftListQuery,
|
||||||
} 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";
|
||||||
|
@ -21,6 +19,7 @@ import usePaginator, {
|
||||||
createPaginationState,
|
createPaginationState,
|
||||||
PaginatorContext,
|
PaginatorContext,
|
||||||
} from "@dashboard/hooks/usePaginator";
|
} from "@dashboard/hooks/usePaginator";
|
||||||
|
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
|
||||||
import { maybe } from "@dashboard/misc";
|
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";
|
||||||
|
@ -29,8 +28,8 @@ import createSortHandler from "@dashboard/utils/handlers/sortHandler";
|
||||||
import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps";
|
import { mapEdgesToItems, mapNodeToChoice } 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 OrderDraftListPage from "../../components/OrderDraftListPage";
|
import OrderDraftListPage from "../../components/OrderDraftListPage";
|
||||||
|
@ -41,14 +40,10 @@ import {
|
||||||
orderUrl,
|
orderUrl,
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import {
|
import {
|
||||||
deleteFilterTab,
|
|
||||||
getActiveFilters,
|
|
||||||
getFilterOpts,
|
getFilterOpts,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
getFiltersCurrentTab,
|
|
||||||
getFilterTabs,
|
|
||||||
getFilterVariables,
|
getFilterVariables,
|
||||||
saveFilterTab,
|
storageUtils,
|
||||||
} from "./filters";
|
} from "./filters";
|
||||||
import { getSortQueryVariables } from "./sort";
|
import { getSortQueryVariables } from "./sort";
|
||||||
|
|
||||||
|
@ -59,16 +54,20 @@ interface OrderDraftListProps {
|
||||||
export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
export const OrderDraftList: React.FC<OrderDraftListProps> = ({ 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.DRAFT_LIST,
|
ListViews.DRAFT_LIST,
|
||||||
);
|
);
|
||||||
|
|
||||||
usePaginationReset(orderDraftListUrl, params, settings.rowNumber);
|
usePaginationReset(orderDraftListUrl, params, settings.rowNumber);
|
||||||
|
|
||||||
const intl = useIntl();
|
const {
|
||||||
|
clearRowSelection,
|
||||||
|
selectedRowIds,
|
||||||
|
setClearDatagridRowSelectionCallback,
|
||||||
|
setSelectedRowIds,
|
||||||
|
} = useRowSelection(params);
|
||||||
|
|
||||||
const [orderDraftBulkDelete, orderDraftBulkDeleteOpts] =
|
const [orderDraftBulkDelete, orderDraftBulkDeleteOpts] =
|
||||||
useOrderDraftBulkCancelMutation({
|
useOrderDraftBulkCancelMutation({
|
||||||
|
@ -82,7 +81,7 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
reset();
|
clearRowSelection();
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -108,17 +107,14 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const tabs = getFilterTabs();
|
|
||||||
|
|
||||||
const currentTab = getFiltersCurrentTab(params, tabs);
|
|
||||||
|
|
||||||
const [changeFilters, resetFilters, handleSearchChange] =
|
const [changeFilters, resetFilters, handleSearchChange] =
|
||||||
createFilterHandlers({
|
createFilterHandlers({
|
||||||
cleanupFn: reset,
|
cleanupFn: clearRowSelection,
|
||||||
createUrl: orderDraftListUrl,
|
createUrl: orderDraftListUrl,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
navigate,
|
navigate,
|
||||||
params,
|
params,
|
||||||
|
keepActiveTab: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
|
@ -126,41 +122,40 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
OrderDraftListUrlQueryParams
|
OrderDraftListUrlQueryParams
|
||||||
>(navigate, orderDraftListUrl, params);
|
>(navigate, orderDraftListUrl, params);
|
||||||
|
|
||||||
const handleTabChange = (tab: number) => {
|
const {
|
||||||
reset();
|
selectedPreset,
|
||||||
navigate(
|
presets,
|
||||||
orderDraftListUrl({
|
hasPresetsChange,
|
||||||
activeTab: tab.toString(),
|
onPresetChange,
|
||||||
...getFilterTabs()[tab - 1].data,
|
onPresetDelete,
|
||||||
}),
|
onPresetSave,
|
||||||
);
|
onPresetUpdate,
|
||||||
};
|
setPresetIdToDelete,
|
||||||
|
presetIdToDelete,
|
||||||
const handleTabDelete = () => {
|
} = useFilterPresets({
|
||||||
deleteFilterTab(currentTab);
|
params,
|
||||||
reset();
|
reset: clearRowSelection,
|
||||||
navigate(orderDraftListUrl());
|
getUrl: orderDraftListUrl,
|
||||||
};
|
storageUtils,
|
||||||
|
});
|
||||||
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
|
|
||||||
saveFilterTab(data.name, getActiveFilters(params));
|
|
||||||
handleTabChange(tabs.length + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
|
|
||||||
const queryVariables = React.useMemo(
|
const queryVariables = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...paginationState,
|
...paginationState,
|
||||||
filter: getFilterVariables(params),
|
filter: getFilterVariables(params),
|
||||||
sort: getSortQueryVariables(params),
|
sort: getSortQueryVariables(params),
|
||||||
}),
|
}),
|
||||||
[params, settings.rowNumber],
|
[paginationState, params],
|
||||||
);
|
);
|
||||||
const { data, loading, refetch } = useOrderDraftListQuery({
|
const { data, loading, refetch } = useOrderDraftListQuery({
|
||||||
displayLoader: true,
|
displayLoader: true,
|
||||||
variables: queryVariables,
|
variables: queryVariables,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const orderDrafts = mapEdgesToItems(data?.draftOrders);
|
||||||
|
|
||||||
const paginationValues = usePaginator({
|
const paginationValues = usePaginator({
|
||||||
pageInfo: maybe(() => data.draftOrders.pageInfo),
|
pageInfo: maybe(() => data.draftOrders.pageInfo),
|
||||||
paginationState,
|
paginationState,
|
||||||
|
@ -172,48 +167,70 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
const onOrderDraftBulkDelete = () =>
|
const onOrderDraftBulkDelete = () =>
|
||||||
orderDraftBulkDelete({
|
orderDraftBulkDelete({
|
||||||
variables: {
|
variables: {
|
||||||
ids: params.ids,
|
ids: selectedRowIds,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSetSelectedOrderDraftIds = useCallback(
|
||||||
|
(rows: number[], clearSelection: () => void) => {
|
||||||
|
if (!orderDrafts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowsIds = rows.map(row => orderDrafts[row].id);
|
||||||
|
const haveSaveValues = isEqual(rowsIds, selectedRowIds);
|
||||||
|
|
||||||
|
if (!haveSaveValues) {
|
||||||
|
setSelectedRowIds(rowsIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
setClearDatagridRowSelectionCallback(clearSelection);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
orderDrafts,
|
||||||
|
selectedRowIds,
|
||||||
|
setClearDatagridRowSelectionCallback,
|
||||||
|
setSelectedRowIds,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaginatorContext.Provider value={paginationValues}>
|
<PaginatorContext.Provider value={paginationValues}>
|
||||||
<OrderDraftListPage
|
<OrderDraftListPage
|
||||||
currentTab={currentTab}
|
selectedFilterPreset={selectedPreset}
|
||||||
filterOpts={getFilterOpts(params)}
|
filterOpts={getFilterOpts(params)}
|
||||||
limits={limitOpts.data?.shop.limits}
|
limits={limitOpts.data?.shop.limits}
|
||||||
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");
|
||||||
|
}}
|
||||||
|
onFilterPresetUpdate={onPresetUpdate}
|
||||||
|
onFilterPresetPresetSave={() => openModal("save-search")}
|
||||||
|
filterPresets={presets.map(tab => tab.name)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
orders={mapEdgesToItems(data?.draftOrders)}
|
orders={orderDrafts}
|
||||||
onAdd={() => openModal("create-order")}
|
onAdd={() => openModal("create-order")}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
onUpdateListSettings={updateListSettings}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
toggle={toggle}
|
currencySymbol={channel?.currencyCode}
|
||||||
toggleAll={toggleAll}
|
hasPresetsChanged={hasPresetsChange}
|
||||||
toolbar={
|
onDraftOrdersDelete={() =>
|
||||||
<IconButton
|
openModal("remove", {
|
||||||
variant="secondary"
|
ids: selectedRowIds,
|
||||||
color="primary"
|
})
|
||||||
onClick={() =>
|
|
||||||
openModal("remove", {
|
|
||||||
ids: listElements,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
}
|
||||||
|
onUpdateListSettings={(...props) => {
|
||||||
|
clearRowSelection();
|
||||||
|
updateListSettings(...props);
|
||||||
|
}}
|
||||||
|
selectedOrderDraftIds={selectedRowIds}
|
||||||
|
onSelectOrderDraftIds={handleSetSelectedOrderDraftIds}
|
||||||
/>
|
/>
|
||||||
<ActionDialog
|
<ActionDialog
|
||||||
confirmButtonState={orderDraftBulkDeleteOpts.status}
|
confirmButtonState={orderDraftBulkDeleteOpts.status}
|
||||||
|
@ -233,9 +250,9 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this order draft?} other{Are you sure you want to delete {displayQuantity} order drafts?}}"
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this order draft?} other{Are you sure you want to delete {displayQuantity} order drafts?}}"
|
||||||
description="dialog content"
|
description="dialog content"
|
||||||
values={{
|
values={{
|
||||||
counter: maybe(() => params.ids.length),
|
counter: maybe(() => selectedRowIds.length),
|
||||||
displayQuantity: (
|
displayQuantity: (
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
<strong>{maybe(() => selectedRowIds.length)}</strong>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -245,14 +262,14 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ 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={presets[presetIdToDelete - 1]?.name ?? "..."}
|
||||||
/>
|
/>
|
||||||
<ChannelPickerDialog
|
<ChannelPickerDialog
|
||||||
channelsChoices={mapNodeToChoice(availableChannels)}
|
channelsChoices={mapNodeToChoice(availableChannels)}
|
||||||
|
|
|
@ -80,8 +80,9 @@ export function getFilterQueryParam(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
|
export const storageUtils = createFilterTabUtils<string>(
|
||||||
createFilterTabUtils<OrderDraftListUrlFilters>(ORDER_DRAFT_FILTERS_KEY);
|
ORDER_DRAFT_FILTERS_KEY,
|
||||||
|
);
|
||||||
|
|
||||||
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
||||||
createFilterUtils<OrderDraftListUrlQueryParams, OrderDraftListUrlFilters>(
|
createFilterUtils<OrderDraftListUrlQueryParams, OrderDraftListUrlFilters>(
|
||||||
|
|
|
@ -105,6 +105,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
||||||
params,
|
params,
|
||||||
defaultSortField: DEFAULT_SORT_KEY,
|
defaultSortField: DEFAULT_SORT_KEY,
|
||||||
hasSortWithRank: true,
|
hasSortWithRank: true,
|
||||||
|
keepActiveTab: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
|
|
19
src/types.ts
19
src/types.ts
|
@ -107,12 +107,31 @@ export interface FilterPageProps<TKeys extends string, TOpts extends {}>
|
||||||
filterOpts: TOpts;
|
filterOpts: TOpts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FilterPagePropsWithPresets<
|
||||||
|
TKeys extends string,
|
||||||
|
TOpts extends {},
|
||||||
|
> extends FilterProps<TKeys>,
|
||||||
|
SearchPageProps,
|
||||||
|
FilterPresetsProps {
|
||||||
|
filterOpts: TOpts;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FilterProps<TKeys extends string> {
|
export interface FilterProps<TKeys extends string> {
|
||||||
currencySymbol?: string;
|
currencySymbol?: string;
|
||||||
onFilterChange: (filter: IFilter<TKeys>) => void;
|
onFilterChange: (filter: IFilter<TKeys>) => void;
|
||||||
onFilterAttributeFocus?: (id?: string) => void;
|
onFilterAttributeFocus?: (id?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FilterPresetsProps {
|
||||||
|
selectedFilterPreset: number;
|
||||||
|
filterPresets: string[];
|
||||||
|
onFilterPresetsAll: () => void;
|
||||||
|
onFilterPresetChange: (id: number) => void;
|
||||||
|
onFilterPresetUpdate: (name: string) => void;
|
||||||
|
onFilterPresetDelete: (id: number) => void;
|
||||||
|
onFilterPresetPresetSave: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TabPageProps {
|
export interface TabPageProps {
|
||||||
currentTab: number;
|
currentTab: number;
|
||||||
tabs: string[];
|
tabs: string[];
|
||||||
|
|
Loading…
Reference in a new issue