From 09c9024e0dc22c267095dada5c6273d074311400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 4 Jul 2023 09:27:17 +0200 Subject: [PATCH] Order drafts list datagrid (#3765) --- .changeset/weak-papayas-grab.md | 5 + locale/defaultMessages.json | 20 +- src/components/AppLayout/TopNav/Root.tsx | 2 +- src/fixtures.ts | 11 + .../OrderDraftList/OrderDraftList.tsx | 237 ------------------ src/orders/components/OrderDraftList/index.ts | 2 - .../OrderDraftListDatagrid.tsx | 150 +++++++++++ .../OrderDraftListDatagrid/datagrid.test.ts | 51 ++++ .../OrderDraftListDatagrid/datagrid.ts | 94 +++++++ .../OrderDraftListDatagrid/index.ts | 1 + .../OrderDraftListDatagrid/messages.ts | 29 +++ .../OrderDraftListDatagrid/utils.ts | 12 + .../OrderDraftListDeleteButton.tsx | 38 +++ .../OrderDraftListDeleteButton/index.ts | 1 + .../OrderDraftListHeader.tsx | 108 ++++++++ .../OrderDraftListPage.stories.tsx | 6 + .../OrderDraftListPage/OrderDraftListPage.tsx | 156 ++++++------ .../views/OrderDraftList/OrderDraftList.tsx | 161 ++++++------ src/orders/views/OrderDraftList/filters.ts | 5 +- src/orders/views/OrderList/OrderList.tsx | 1 + src/types.ts | 19 ++ 21 files changed, 712 insertions(+), 397 deletions(-) create mode 100644 .changeset/weak-papayas-grab.md delete mode 100644 src/orders/components/OrderDraftList/OrderDraftList.tsx delete mode 100644 src/orders/components/OrderDraftList/index.ts create mode 100644 src/orders/components/OrderDraftListDatagrid/OrderDraftListDatagrid.tsx create mode 100644 src/orders/components/OrderDraftListDatagrid/datagrid.test.ts create mode 100644 src/orders/components/OrderDraftListDatagrid/datagrid.ts create mode 100644 src/orders/components/OrderDraftListDatagrid/index.ts create mode 100644 src/orders/components/OrderDraftListDatagrid/messages.ts create mode 100644 src/orders/components/OrderDraftListDatagrid/utils.ts create mode 100644 src/orders/components/OrderDraftListDeleteButton/OrderDraftListDeleteButton.tsx create mode 100644 src/orders/components/OrderDraftListDeleteButton/index.ts create mode 100644 src/orders/components/OrderDraftListHeader/OrderDraftListHeader.tsx diff --git a/.changeset/weak-papayas-grab.md b/.changeset/weak-papayas-grab.md new file mode 100644 index 000000000..7bd845e69 --- /dev/null +++ b/.changeset/weak-papayas-grab.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Introduce datagrid on order draft list diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 9805f25aa..d3b0a7f0e 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1330,10 +1330,6 @@ "context": "staff member status", "string": "Inactive" }, - "7a1S4K": { - "context": "tab name", - "string": "All Drafts" - }, "7dhhzL": { "context": "bulk issue gift cards dialog title", "string": "Bulk Issue Gift Cards" @@ -2929,6 +2925,9 @@ "context": "all authorized amount from transactions in order", "string": "Authorized" }, + "IzECoP": { + "string": "Search draft orders..." + }, "IzEVek": { "context": "bulk disable label", "string": "Deactivate" @@ -3138,9 +3137,6 @@ "KHZlmi": { "string": "Discount Type" }, - "KIh25E": { - "string": "No draft orders found" - }, "KKQUMK": { "context": "edit menu item, header", "string": "Edit Item" @@ -3486,9 +3482,6 @@ "NGc9kE": { "string": "Page type deleted" }, - "NJEe12": { - "string": "Search Draft" - }, "NJbzcP": { "context": "dialog header", "string": "Cancel Orders" @@ -5103,6 +5096,9 @@ "context": "no address is set in draft order", "string": "Not set" }, + "YJ2uRR": { + "string": "Bulk delete draft orders" + }, "YJ4TXc": { "context": "tab name", "string": "All Staff Members" @@ -7061,6 +7057,10 @@ "context": "section header", "string": "Ongoing Installations" }, + "nJ0tek": { + "context": "tab name", + "string": "All draft orders" + }, "nKjLjT": { "context": "error message", "string": "Slug must be unique for each warehouse" diff --git a/src/components/AppLayout/TopNav/Root.tsx b/src/components/AppLayout/TopNav/Root.tsx index 2631cecc0..d29512757 100644 --- a/src/components/AppLayout/TopNav/Root.tsx +++ b/src/components/AppLayout/TopNav/Root.tsx @@ -26,7 +26,7 @@ export const Root: React.FC> = ({ return ( {href && } - + {title} diff --git a/src/fixtures.ts b/src/fixtures.ts index b39b0cccc..c89f3323b 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -10,6 +10,7 @@ import { PaginatorContextValues } from "./hooks/usePaginator"; import { FetchMoreProps, FilterPageProps, + FilterPresetsProps, ListActions, SearchPageProps, SortPage, @@ -305,6 +306,16 @@ export const tabPageProps: TabPageProps = { 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 = { endCursor: "", startCursor: "", diff --git a/src/orders/components/OrderDraftList/OrderDraftList.tsx b/src/orders/components/OrderDraftList/OrderDraftList.tsx deleted file mode 100644 index e2a765978..000000000 --- a/src/orders/components/OrderDraftList/OrderDraftList.tsx +++ /dev/null @@ -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 { - orders: RelayToFlat; -} - -export const OrderDraftList: React.FC = 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 ( - - - onSort(OrderDraftListUrlSortField.number)} - className={classes.colNumber} - > - - - onSort(OrderDraftListUrlSortField.date)} - className={classes.colDate} - > - - - onSort(OrderDraftListUrlSortField.customer)} - className={classes.colCustomer} - > - - - - - - - - - - - - - {renderCollection( - orderDraftList, - order => { - const isSelected = order ? isChecked(order.id) : false; - - return ( - - - toggle(order.id)} - /> - - - {maybe(() => order.number) ? ( - "#" + order.number - ) : ( - - )} - - - {maybe(() => order.created) ? ( - - ) : ( - - )} - - - {maybe(() => order.billingAddress) ? ( - <> - {order.billingAddress.firstName} -   - {order.billingAddress.lastName} - - ) : maybe(() => order.userEmail) !== undefined ? ( - order.userEmail - ) : ( - - )} - - - {maybe(() => order.total.gross) ? ( - - ) : ( - - )} - - - ); - }, - () => ( - - - - - - ), - )} - - - ); -}; -OrderDraftList.displayName = "OrderDraftList"; -export default OrderDraftList; diff --git a/src/orders/components/OrderDraftList/index.ts b/src/orders/components/OrderDraftList/index.ts deleted file mode 100644 index 5733dc916..000000000 --- a/src/orders/components/OrderDraftList/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./OrderDraftList"; -export * from "./OrderDraftList"; diff --git a/src/orders/components/OrderDraftListDatagrid/OrderDraftListDatagrid.tsx b/src/orders/components/OrderDraftListDatagrid/OrderDraftListDatagrid.tsx new file mode 100644 index 000000000..5e15ba6bc --- /dev/null +++ b/src/orders/components/OrderDraftListDatagrid/OrderDraftListDatagrid.tsx @@ -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 { + orders: RelayToFlat; + 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 ( + + 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 => ( + undefined} + onQueryChange={picker.setQuery} + query={picker.query} + /> + )} + /> + + + + + + ); +}; diff --git a/src/orders/components/OrderDraftListDatagrid/datagrid.test.ts b/src/orders/components/OrderDraftListDatagrid/datagrid.test.ts new file mode 100644 index 000000000..730d342ac --- /dev/null +++ b/src/orders/components/OrderDraftListDatagrid/datagrid.test.ts @@ -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>[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>[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 + >[number]; + + // Act + const result = getCustomerName(data); + + // Assert + expect(result).toEqual("-"); + }); +}); diff --git a/src/orders/components/OrderDraftListDatagrid/datagrid.ts b/src/orders/components/OrderDraftListDatagrid/datagrid.ts new file mode 100644 index 000000000..84b283966 --- /dev/null +++ b/src/orders/components/OrderDraftListDatagrid/datagrid.ts @@ -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; + 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[number], +) { + if (rowData?.billingAddress?.firstName && rowData?.billingAddress?.lastName) { + return `${rowData.billingAddress.firstName} ${rowData.billingAddress.lastName}`; + } + + if (rowData.userEmail) { + return rowData.userEmail; + } + + return "-"; +} diff --git a/src/orders/components/OrderDraftListDatagrid/index.ts b/src/orders/components/OrderDraftListDatagrid/index.ts new file mode 100644 index 000000000..0fcf887e7 --- /dev/null +++ b/src/orders/components/OrderDraftListDatagrid/index.ts @@ -0,0 +1 @@ +export * from "./OrderDraftListDatagrid"; diff --git a/src/orders/components/OrderDraftListDatagrid/messages.ts b/src/orders/components/OrderDraftListDatagrid/messages.ts new file mode 100644 index 000000000..11a402926 --- /dev/null +++ b/src/orders/components/OrderDraftListDatagrid/messages.ts @@ -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", + }, +}); diff --git a/src/orders/components/OrderDraftListDatagrid/utils.ts b/src/orders/components/OrderDraftListDatagrid/utils.ts new file mode 100644 index 000000000..77df8067a --- /dev/null +++ b/src/orders/components/OrderDraftListDatagrid/utils.ts @@ -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; + } +} diff --git a/src/orders/components/OrderDraftListDeleteButton/OrderDraftListDeleteButton.tsx b/src/orders/components/OrderDraftListDeleteButton/OrderDraftListDeleteButton.tsx new file mode 100644 index 000000000..817700d3b --- /dev/null +++ b/src/orders/components/OrderDraftListDeleteButton/OrderDraftListDeleteButton.tsx @@ -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 ( + + + + + {hasLimits(limits, "orders") && ( + + )} + + + + ); +}; diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.stories.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.stories.tsx index ab1828ec3..52ddd9304 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.stories.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.stories.tsx @@ -1,6 +1,7 @@ // @ts-strict-ignore import { filterPageProps, + filterPresetsProps, limits, limitsReached, listActionsProps, @@ -22,6 +23,7 @@ const props: OrderDraftListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...filterPresetsProps, ...sortPageProps, ...tabPageProps, ...filterPageProps, @@ -45,6 +47,10 @@ const props: OrderDraftListPageProps = { ...sortPageProps.sort, sort: OrderDraftListUrlSortField.number, }, + onDraftOrdersDelete: () => undefined, + onSelectOrderDraftIds: () => undefined, + selectedOrderDraftIds: [], + hasPresetsChanged: () => false, }; const meta: Meta = { diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 33c41cdd6..216269cdd 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -1,25 +1,22 @@ // @ts-strict-ignore -import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo"; -import { TopNav } from "@dashboard/components/AppLayout/TopNav"; -import { Button } from "@dashboard/components/Button"; -import FilterBar from "@dashboard/components/FilterBar"; +import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { OrderDraftListQuery, RefreshLimitsQuery } from "@dashboard/graphql"; -import { sectionNames } from "@dashboard/intl"; import { OrderDraftListUrlSortField } from "@dashboard/orders/urls"; import { - FilterPageProps, - ListActions, + FilterPagePropsWithPresets, PageListProps, RelayToFlat, SortPage, - TabPageProps, } from "@dashboard/types"; -import { hasLimits, isLimitReached } from "@dashboard/utils/limits"; +import { isLimitReached } from "@dashboard/utils/limits"; import { Card } from "@material-ui/core"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; +import { Box } from "@saleor/macaw-ui/next"; +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 { createFilterStructure, @@ -29,90 +26,103 @@ import { export interface OrderDraftListPageProps extends PageListProps, - ListActions, - FilterPageProps, - SortPage, - TabPageProps { + FilterPagePropsWithPresets, + SortPage { limits: RefreshLimitsQuery["shop"]["limits"]; orders: RelayToFlat; + selectedOrderDraftIds: string[]; + hasPresetsChanged: () => boolean; onAdd: () => void; + onDraftOrdersDelete: () => void; + onSelectOrderDraftIds: (ids: number[], clearSelection: () => void) => void; } const OrderDraftListPage: React.FC = ({ - currentTab, + selectedFilterPreset, disabled, filterOpts, initialSearch, limits, onAdd, - onAll, + onFilterPresetsAll, onFilterChange, onSearchChange, - onTabChange, - onTabDelete, - onTabSave, - tabs, + onFilterPresetChange, + onFilterPresetDelete, + onFilterPresetUpdate, + onFilterPresetPresetSave, + filterPresets, + hasPresetsChanged, + onDraftOrdersDelete, + onFilterAttributeFocus, + currencySymbol, + selectedOrderDraftIds, ...listProps }) => { const intl = useIntl(); - const structure = createFilterStructure(intl, filterOpts); + const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); + const filterStructure = createFilterStructure(intl, filterOpts); const limitsReached = isLimitReached(limits, "orders"); return ( <> - - - {hasLimits(limits, "orders") && ( - - )} - + + {limitsReached && } + - + + {selectedOrderDraftIds.length > 0 && ( + + {intl.formatMessage({ + id: "YJ2uRR", + defaultMessage: "Bulk delete draft orders", + })} + + )} + + } + /> + + + - ); diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 11dab5ae6..0c094575a 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -3,16 +3,14 @@ import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDia import ActionDialog from "@dashboard/components/ActionDialog"; import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext"; import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; -import SaveFilterTabDialog, { - SaveFilterTabDialogFormData, -} from "@dashboard/components/SaveFilterTabDialog"; +import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { useShopLimitsQuery } from "@dashboard/components/Shop/queries"; import { useOrderDraftBulkCancelMutation, useOrderDraftCreateMutation, useOrderDraftListQuery, } from "@dashboard/graphql"; -import useBulkActions from "@dashboard/hooks/useBulkActions"; +import { useFilterPresets } from "@dashboard/hooks/useFilterPresets"; import useListSettings from "@dashboard/hooks/useListSettings"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; @@ -21,6 +19,7 @@ import usePaginator, { createPaginationState, PaginatorContext, } from "@dashboard/hooks/usePaginator"; +import { useRowSelection } from "@dashboard/hooks/useRowSelection"; import { maybe } from "@dashboard/misc"; import { ListViews } from "@dashboard/types"; 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 { getSortParams } from "@dashboard/utils/sort"; import { DialogContentText } from "@material-ui/core"; -import { DeleteIcon, IconButton } from "@saleor/macaw-ui"; -import React from "react"; +import isEqual from "lodash/isEqual"; +import React, { useCallback } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import OrderDraftListPage from "../../components/OrderDraftListPage"; @@ -41,14 +40,10 @@ import { orderUrl, } from "../../urls"; import { - deleteFilterTab, - getActiveFilters, getFilterOpts, getFilterQueryParam, - getFiltersCurrentTab, - getFilterTabs, getFilterVariables, - saveFilterTab, + storageUtils, } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -59,16 +54,20 @@ interface OrderDraftListProps { export const OrderDraftList: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); - const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( - params.ids, - ); + const intl = useIntl(); + const { updateListSettings, settings } = useListSettings( ListViews.DRAFT_LIST, ); usePaginationReset(orderDraftListUrl, params, settings.rowNumber); - const intl = useIntl(); + const { + clearRowSelection, + selectedRowIds, + setClearDatagridRowSelectionCallback, + setSelectedRowIds, + } = useRowSelection(params); const [orderDraftBulkDelete, orderDraftBulkDeleteOpts] = useOrderDraftBulkCancelMutation({ @@ -82,7 +81,7 @@ export const OrderDraftList: React.FC = ({ params }) => { }), }); refetch(); - reset(); + clearRowSelection(); closeModal(); } }, @@ -108,17 +107,14 @@ export const OrderDraftList: React.FC = ({ params }) => { }, }); - const tabs = getFilterTabs(); - - const currentTab = getFiltersCurrentTab(params, tabs); - const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ - cleanupFn: reset, + cleanupFn: clearRowSelection, createUrl: orderDraftListUrl, getFilterQueryParam, navigate, params, + keepActiveTab: true, }); const [openModal, closeModal] = createDialogActionHandlers< @@ -126,41 +122,40 @@ export const OrderDraftList: React.FC = ({ params }) => { OrderDraftListUrlQueryParams >(navigate, orderDraftListUrl, params); - const handleTabChange = (tab: number) => { - reset(); - navigate( - orderDraftListUrl({ - activeTab: tab.toString(), - ...getFilterTabs()[tab - 1].data, - }), - ); - }; - - const handleTabDelete = () => { - deleteFilterTab(currentTab); - reset(); - navigate(orderDraftListUrl()); - }; - - const handleTabSave = (data: SaveFilterTabDialogFormData) => { - saveFilterTab(data.name, getActiveFilters(params)); - handleTabChange(tabs.length + 1); - }; + const { + selectedPreset, + presets, + hasPresetsChange, + onPresetChange, + onPresetDelete, + onPresetSave, + onPresetUpdate, + setPresetIdToDelete, + presetIdToDelete, + } = useFilterPresets({ + params, + reset: clearRowSelection, + getUrl: orderDraftListUrl, + storageUtils, + }); const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( () => ({ ...paginationState, filter: getFilterVariables(params), sort: getSortQueryVariables(params), }), - [params, settings.rowNumber], + [paginationState, params], ); const { data, loading, refetch } = useOrderDraftListQuery({ displayLoader: true, variables: queryVariables, }); + const orderDrafts = mapEdgesToItems(data?.draftOrders); + const paginationValues = usePaginator({ pageInfo: maybe(() => data.draftOrders.pageInfo), paginationState, @@ -172,48 +167,70 @@ export const OrderDraftList: React.FC = ({ params }) => { const onOrderDraftBulkDelete = () => orderDraftBulkDelete({ 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 ( openModal("delete-search")} - onTabSave={() => openModal("save-search")} - tabs={tabs.map(tab => tab.name)} + onFilterPresetsAll={resetFilters} + onFilterPresetChange={onPresetChange} + onFilterPresetDelete={(id: number) => { + setPresetIdToDelete(id); + openModal("delete-search"); + }} + onFilterPresetUpdate={onPresetUpdate} + onFilterPresetPresetSave={() => openModal("save-search")} + filterPresets={presets.map(tab => tab.name)} disabled={loading} settings={settings} - orders={mapEdgesToItems(data?.draftOrders)} + orders={orderDrafts} onAdd={() => openModal("create-order")} onSort={handleSort} - onUpdateListSettings={updateListSettings} - isChecked={isSelected} - selected={listElements.length} sort={getSortParams(params)} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - openModal("remove", { - ids: listElements, - }) - } - > - - + currencySymbol={channel?.currencyCode} + hasPresetsChanged={hasPresetsChange} + onDraftOrdersDelete={() => + openModal("remove", { + ids: selectedRowIds, + }) } + onUpdateListSettings={(...props) => { + clearRowSelection(); + updateListSettings(...props); + }} + selectedOrderDraftIds={selectedRowIds} + onSelectOrderDraftIds={handleSetSelectedOrderDraftIds} /> = ({ 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?}}" description="dialog content" values={{ - counter: maybe(() => params.ids.length), + counter: maybe(() => selectedRowIds.length), displayQuantity: ( - {maybe(() => params.ids.length)} + {maybe(() => selectedRowIds.length)} ), }} /> @@ -245,14 +262,14 @@ export const OrderDraftList: React.FC = ({ params }) => { open={params.action === "save-search"} confirmButtonState="default" onClose={closeModal} - onSubmit={handleTabSave} + onSubmit={onPresetSave} /> tabs[currentTab - 1].name, "...")} + onSubmit={onPresetDelete} + tabName={presets[presetIdToDelete - 1]?.name ?? "..."} /> (ORDER_DRAFT_FILTERS_KEY); +export const storageUtils = createFilterTabUtils( + ORDER_DRAFT_FILTERS_KEY, +); export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils( diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 308f4a094..985092344 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -105,6 +105,7 @@ export const OrderList: React.FC = ({ params }) => { params, defaultSortField: DEFAULT_SORT_KEY, hasSortWithRank: true, + keepActiveTab: true, }); const [openModal, closeModal] = createDialogActionHandlers< diff --git a/src/types.ts b/src/types.ts index c70f20c1f..c479f7759 100644 --- a/src/types.ts +++ b/src/types.ts @@ -107,12 +107,31 @@ export interface FilterPageProps filterOpts: TOpts; } +export interface FilterPagePropsWithPresets< + TKeys extends string, + TOpts extends {}, +> extends FilterProps, + SearchPageProps, + FilterPresetsProps { + filterOpts: TOpts; +} + export interface FilterProps { currencySymbol?: string; onFilterChange: (filter: IFilter) => 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 { currentTab: number; tabs: string[];