Introduce datagrid on Discounts list (#3939)

Co-authored-by: wojteknowacki <wojciech.nowacki@saleor.io>
This commit is contained in:
Paweł Chyła 2023-07-19 15:31:13 +02:00 committed by GitHub
parent 33b4199cec
commit 52f58eb00a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 622 additions and 462 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": minor
---
Introduct datagrid on discounts list page

View file

@ -166,9 +166,7 @@ describe("As an admin I want to create sale for products", () => {
cy.clearSessionData() cy.clearSessionData()
.loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount) .loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount)
*/ */
cy.visit(urlList.sales) cy.visit(urlList.sales);
.expectSkeletonIsVisible()
.waitForProgressBarToNotExist();
createSale({ createSale({
saleName, saleName,
channelName: channel.name, channelName: channel.name,

View file

@ -24,7 +24,6 @@ export function createSale({
cy.get(SALES_SELECTORS.createSaleButton) cy.get(SALES_SELECTORS.createSaleButton)
.click() .click()
.waitForProgressBarToNotBeVisible()
.get(SALES_SELECTORS.nameInput) .get(SALES_SELECTORS.nameInput)
.type(saleName) .type(saleName)
.get(discountOption) .get(discountOption)
@ -37,7 +36,6 @@ export function createSale({
.addAliasToGraphRequest("SaleCreate") .addAliasToGraphRequest("SaleCreate")
.get(SALES_SELECTORS.saveButton) .get(SALES_SELECTORS.saveButton)
.click() .click()
.confirmationMessageShouldDisappear()
.waitForRequestAndCheckIfNoErrors("@SaleCreate"); .waitForRequestAndCheckIfNoErrors("@SaleCreate");
} }
@ -102,9 +100,7 @@ export function createSaleWithNewProduct({
cy.clearSessionData() cy.clearSessionData()
.loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount) .loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount)
*/ */
cy.visit(urlList.sales) cy.visit(urlList.sales);
.expectSkeletonIsVisible()
.waitForProgressBarToNotExist();
createSale({ createSale({
saleName: name, saleName: name,
channelName: channel.name, channelName: channel.name,
@ -141,9 +137,7 @@ export function createSaleWithNewVariant({
cy.clearSessionData() cy.clearSessionData()
.loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount) .loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount)
*/ */
cy.visit(urlList.sales) cy.visit(urlList.sales);
.expectSkeletonIsVisible()
.waitForProgressBarToNotExist();
createSale({ createSale({
saleName: name, saleName: name,
channelName: channel.name, channelName: channel.name,

View file

@ -96,6 +96,9 @@
"context": "button", "context": "button",
"string": "Activate" "string": "Activate"
}, },
"+bhokL": {
"string": "Search discounts..."
},
"+do3gl": { "+do3gl": {
"context": "input helper text", "context": "input helper text",
"string": "This number defines quantity of items in checkout line that can be bought. You can override this setting per variant. Leaving this setting empty mean that there is no limits." "string": "This number defines quantity of items in checkout line that can be bought. You can override this setting per variant. Leaving this setting empty mean that there is no limits."
@ -2758,6 +2761,9 @@
"context": "create gift card product alert message", "context": "create gift card product alert message",
"string": "Create a gift card product" "string": "Create a gift card product"
}, },
"Hswqx2": {
"string": "Delete discounts"
},
"HvJPcU": { "HvJPcU": {
"string": "Category deleted" "string": "Category deleted"
}, },
@ -5317,6 +5323,10 @@
"a5msuh": { "a5msuh": {
"string": "Yes" "string": "Yes"
}, },
"a6GDem": {
"context": "tab name",
"string": "All discounts"
},
"a9S9Je": { "a9S9Je": {
"context": "page types section name", "context": "page types section name",
"string": "Page Types" "string": "Page Types"
@ -5621,10 +5631,6 @@
"c8nvms": { "c8nvms": {
"string": "Sales" "string": "Sales"
}, },
"c8zJID": {
"context": "tab name",
"string": "All Discounts"
},
"cBHRxx": { "cBHRxx": {
"context": "button", "context": "button",
"string": "Assign Warehouse" "string": "Assign Warehouse"
@ -6828,9 +6834,6 @@
"context": "export items as csv file", "context": "export items as csv file",
"string": "Plain CSV file" "string": "Plain CSV file"
}, },
"lit2zF": {
"string": "Search Discounts"
},
"ll2dE6": { "ll2dE6": {
"context": "PageTypeDeleteWarningDialog multiple assigned items description", "context": "PageTypeDeleteWarningDialog multiple assigned items description",
"string": "Are you sure you want to delete selected page types? If you remove them you wont be able to assign them to created pages." "string": "Are you sure you want to delete selected page types? If you remove them you wont be able to assign them to created pages."

View file

@ -17,7 +17,7 @@ export interface ListFiltersProps<TKeys extends string = string>
actions?: ReactNode; actions?: ReactNode;
} }
export const ListFilters = ({ export const ListFilters = <TFilterKeys extends string = string>({
currencySymbol, currencySymbol,
filterStructure, filterStructure,
initialSearch, initialSearch,
@ -27,7 +27,7 @@ export const ListFilters = ({
onFilterAttributeFocus, onFilterAttributeFocus,
errorMessages, errorMessages,
actions, actions,
}: ListFiltersProps) => { }: ListFiltersProps<TFilterKeys>) => {
const isProductPage = window.location.pathname.includes("/products"); const isProductPage = window.location.pathname.includes("/products");
const productListingPageFiltersFlag = useFlag("product_filters"); const productListingPageFiltersFlag = useFlag("product_filters");
const filtersEnabled = isProductPage && productListingPageFiltersFlag.enabled; const filtersEnabled = isProductPage && productListingPageFiltersFlag.enabled;
@ -45,7 +45,7 @@ export const ListFilters = ({
{filtersEnabled ? ( {filtersEnabled ? (
<ExpressionFilters /> <ExpressionFilters />
) : ( ) : (
<FiltersSelect <FiltersSelect<TFilterKeys>
errorMessages={errorMessages} errorMessages={errorMessages}
menu={filterStructure} menu={filterStructure}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}

View file

@ -19,17 +19,17 @@ export interface FilterProps<TFilterKeys extends string = string> {
currencySymbol?: string; currencySymbol?: string;
errorMessages?: FilterErrorMessages<TFilterKeys>; errorMessages?: FilterErrorMessages<TFilterKeys>;
menu: IFilter<TFilterKeys>; menu: IFilter<TFilterKeys>;
onFilterAdd: (filter: Array<FilterElement<string>>) => void; onFilterAdd: (filter: Array<FilterElement<TFilterKeys>>) => void;
onFilterAttributeFocus?: (id?: string) => void; onFilterAttributeFocus?: (id?: string) => void;
} }
export const FiltersSelect = ({ export const FiltersSelect = <TFilterKeys extends string = string>({
currencySymbol, currencySymbol,
menu, menu,
onFilterAdd, onFilterAdd,
onFilterAttributeFocus, onFilterAttributeFocus,
errorMessages, errorMessages,
}: FilterProps) => { }: FilterProps<TFilterKeys>) => {
const anchor = React.useRef<HTMLDivElement>(); const anchor = React.useRef<HTMLDivElement>();
const [isFilterMenuOpened, setFilterMenuOpened] = useState(false); const [isFilterMenuOpened, setFilterMenuOpened] = useState(false);
const [filterErrors, setFilterErrors] = useState<InvalidFilters<string>>({}); const [filterErrors, setFilterErrors] = useState<InvalidFilters<string>>({});

View file

@ -0,0 +1 @@
export const PLACEHOLDER = "-";

View file

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { LocaleConsumer } from "../Locale"; import { LocaleConsumer } from "../Locale";
import { formatPercantage } from "./utils";
interface PercentProps { interface PercentProps {
amount: number; amount: number;
@ -8,14 +9,7 @@ interface PercentProps {
const Percent: React.FC<PercentProps> = ({ amount }) => ( const Percent: React.FC<PercentProps> = ({ amount }) => (
<LocaleConsumer> <LocaleConsumer>
{({ locale }) => {({ locale }) => formatPercantage(amount, locale)}
amount
? (amount / 100).toLocaleString(locale, {
maximumFractionDigits: 2,
style: "percent",
})
: "-"
}
</LocaleConsumer> </LocaleConsumer>
); );
Percent.displayName = "Percent"; Percent.displayName = "Percent";

View file

@ -0,0 +1,17 @@
import { Locale } from "../Locale";
import { formatPercantage } from "./utils";
describe("formatPercantage", () => {
it('should return "-" when amount is 0', () => {
expect(formatPercantage(0, Locale.EN)).toBe("-");
});
it('should return "-" when amount is undefined', () => {
expect(formatPercantage(undefined, Locale.EN)).toBe("-");
});
it("should return percantage when amount is provided", () => {
expect(formatPercantage(33, Locale.EN)).toBe("33%");
expect(formatPercantage(33.1233, Locale.EN)).toBe("33.12%");
});
});

View file

@ -0,0 +1,13 @@
import { Locale } from "../Locale";
export const formatPercantage = (
amount: number | undefined,
locale: Locale,
) => {
return amount
? (amount / 100).toLocaleString(locale, {
maximumFractionDigits: 2,
style: "percent",
})
: "-";
};

View file

@ -111,6 +111,7 @@ export const defaultListSettings: AppListViewSettings = {
}, },
[ListViews.SALES_LIST]: { [ListViews.SALES_LIST]: {
rowNumber: PAGINATE_BY, rowNumber: PAGINATE_BY,
columns: ["name", "startDate", "endDate", "value"],
}, },
[ListViews.SHIPPING_METHODS_LIST]: { [ListViews.SHIPPING_METHODS_LIST]: {
rowNumber: PAGINATE_BY, rowNumber: PAGINATE_BY,

View file

@ -1,268 +0,0 @@
// @ts-strict-ignore
import Checkbox from "@dashboard/components/Checkbox";
import Date from "@dashboard/components/Date";
import Money from "@dashboard/components/Money";
import Percent from "@dashboard/components/Percent";
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 TooltipTableCellHeader from "@dashboard/components/TooltipTableCellHeader";
import { commonTooltipMessages } from "@dashboard/components/TooltipTableCellHeader/messages";
import { SaleListUrlSortField, saleUrl } from "@dashboard/discounts/urls";
import { canBeSorted } from "@dashboard/discounts/views/SaleList/sort";
import { SaleFragment, SaleType } from "@dashboard/graphql";
import { maybe, renderCollection } from "@dashboard/misc";
import {
ChannelProps,
ListActions,
ListProps,
SortPage,
} from "@dashboard/types";
import { getArrowDirection } from "@dashboard/utils/sort";
import { TableBody, TableCell, TableFooter } from "@material-ui/core";
import { makeStyles } from "@saleor/macaw-ui";
import clsx from "clsx";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface SaleListProps
extends ListProps,
ListActions,
SortPage<SaleListUrlSortField>,
ChannelProps {
sales: SaleFragment[];
}
const useStyles = makeStyles(
theme => ({
[theme.breakpoints.up("lg")]: {
colEnd: {
width: 250,
},
colName: {},
colStart: {
width: 250,
},
colValue: {
width: 200,
},
},
colEnd: {
textAlign: "right",
},
colName: {
paddingLeft: 0,
},
colStart: {
textAlign: "right",
},
colValue: {
textAlign: "right",
},
tableRow: {
cursor: "pointer",
},
textOverflow: {
textOverflow: "ellipsis",
overflow: "hidden",
},
}),
{ name: "SaleList" },
);
const SaleList: React.FC<SaleListProps> = props => {
const {
settings,
disabled,
onUpdateListSettings,
onSort,
sales,
selectedChannelId,
isChecked,
selected,
sort,
toggle,
toggleAll,
toolbar,
filterDependency,
} = props;
const classes = useStyles(props);
const intl = useIntl();
const numberOfColumns = sales?.length === 0 ? 4 : 5;
return (
<ResponsiveTable>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={sales}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCellHeader
direction={
sort.sort === SaleListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(SaleListUrlSortField.name)}
className={classes.colName}
>
<FormattedMessage
id="F56hOz"
defaultMessage="Name"
description="sale name"
/>
</TableCellHeader>
<TableCellHeader
direction={
sort.sort === SaleListUrlSortField.startDate
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(SaleListUrlSortField.startDate)}
className={classes.colStart}
>
<FormattedMessage
id="iBSq6l"
defaultMessage="Starts"
description="sale start date"
/>
</TableCellHeader>
<TableCellHeader
direction={
sort.sort === SaleListUrlSortField.endDate
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(SaleListUrlSortField.endDate)}
className={classes.colEnd}
>
<FormattedMessage
id="giF5UV"
defaultMessage="Ends"
description="sale end date"
/>
</TableCellHeader>
<TooltipTableCellHeader
direction={
sort.sort === SaleListUrlSortField.value
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(SaleListUrlSortField.value)}
disabled={
!canBeSorted(SaleListUrlSortField.value, !!selectedChannelId)
}
tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, {
filterName: filterDependency.label,
})}
className={classes.colValue}
>
<FormattedMessage
id="XZR590"
defaultMessage="Value"
description="sale value"
/>
</TooltipTableCellHeader>
</TableHead>
<TableFooter>
<TableRowLink>
<TablePaginationWithContext
colSpan={numberOfColumns}
settings={settings}
onUpdateListSettings={onUpdateListSettings}
/>
</TableRowLink>
</TableFooter>
<TableBody>
{renderCollection(
sales,
sale => {
const isSelected = sale ? isChecked(sale.id) : false;
const channel = sale?.channelListings?.find(
lisiting => lisiting.channel.id === selectedChannelId,
);
return (
<TableRowLink
className={!!sale ? classes.tableRow : undefined}
hover={!!sale}
key={sale ? sale.id : "skeleton"}
href={sale && saleUrl(sale.id)}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(sale.id)}
/>
</TableCell>
<TableCell
className={clsx(classes.colName, classes.textOverflow)}
>
{maybe<React.ReactNode>(() => sale.name, <Skeleton />)}
</TableCell>
<TableCell className={classes.colStart}>
{sale && sale.startDate ? (
<Date date={sale.startDate} plain />
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colEnd}>
{sale && sale.endDate ? (
<Date date={sale.endDate} plain />
) : sale && sale.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colValue}>
{sale?.type && channel?.discountValue ? (
sale.type === SaleType.FIXED ? (
<Money
money={{
amount: channel.discountValue,
currency: channel.currency,
}}
/>
) : channel?.discountValue ? (
<Percent amount={channel.discountValue} />
) : (
"-"
)
) : sale && !channel ? (
"_"
) : (
<Skeleton />
)}
</TableCell>
</TableRowLink>
);
},
() => (
<TableRowLink>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage id="51HE+Q" defaultMessage="No sales found" />
</TableCell>
</TableRowLink>
),
)}
</TableBody>
</ResponsiveTable>
);
};
SaleList.displayName = "SaleList";
export default SaleList;

View file

@ -1,2 +0,0 @@
export { default } from "./SaleList";
export * from "./SaleList";

View file

@ -0,0 +1,175 @@
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 { commonTooltipMessages } from "@dashboard/components/TooltipTableCellHeader/messages";
import { SaleListUrlSortField, saleUrl } from "@dashboard/discounts/urls";
import { SaleFragment } from "@dashboard/graphql";
import useLocale from "@dashboard/hooks/useLocale";
import { ChannelProps, 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 { canBeSorted } from "../../views/SaleList/sort";
import {
createGetCellContent,
salesListStaticColumnsAdapter,
} from "./datagrid";
import { messages } from "./messages";
interface SaleListDatagridProps
extends ListProps,
SortPage<SaleListUrlSortField>,
ChannelProps {
sales: SaleFragment[];
onSelectSaleIds: (ids: number[], clearSelection: () => void) => void;
onRowClick: (id: string) => void;
hasRowHover?: boolean;
}
export const SaleListDatagrid = ({
disabled,
onSort,
sales,
selectedChannelId,
sort,
filterDependency,
onUpdateListSettings,
onSelectSaleIds,
onRowClick,
hasRowHover = true,
settings,
}: SaleListDatagridProps) => {
const intl = useIntl();
const { locale } = useLocale();
const datagrid = useDatagridChangeState();
const collectionListStaticColumns = useMemo(
() => salesListStaticColumnsAdapter(intl, sort),
[intl, sort],
);
const onColumnChange = useCallback(
(picked: string[]) => {
if (onUpdateListSettings) {
onUpdateListSettings("columns", picked.filter(Boolean));
}
},
[onUpdateListSettings],
);
const {
handlers,
visibleColumns,
staticColumns,
selectedColumns,
recentlyAddedColumn,
} = useColumns({
staticColumns: collectionListStaticColumns,
selectedColumns: settings?.columns ?? [],
onSave: onColumnChange,
});
const getCellContent = useCallback(
createGetCellContent({
sales,
columns: visibleColumns,
locale,
selectedChannelId,
}),
[sales, selectedChannelId, locale, visibleColumns],
);
const handleRowClick = useCallback(
([_, row]: Item) => {
if (!onRowClick) {
return;
}
const rowData: SaleFragment = sales[row];
onRowClick(rowData.id);
},
[onRowClick, sales],
);
const handleRowAnchor = useCallback(
([, row]: Item) => saleUrl(sales[row].id),
[sales],
);
const handleGetColumnTooltipContent = useCallback(
(col: number): string => {
const columnName = visibleColumns[col].id as SaleListUrlSortField;
if (canBeSorted(columnName, !!selectedChannelId)) {
return "";
}
// Sortable but requrie selected channel
return intl.formatMessage(commonTooltipMessages.noFilterSelected, {
filterName: filterDependency?.label ?? "",
});
},
[filterDependency, intl, selectedChannelId, visibleColumns],
);
const handleHeaderClick = useCallback(
(col: number) => {
const columnName = visibleColumns[col].id as SaleListUrlSortField;
if (canBeSorted(columnName, !!selectedChannelId)) {
onSort(columnName);
}
},
[visibleColumns, onSort],
);
return (
<DatagridChangeStateContext.Provider value={datagrid}>
<Datagrid
readonly
loading={disabled}
rowMarkers="checkbox"
columnSelect="single"
hasRowHover={hasRowHover}
onColumnMoved={handlers.onMove}
onColumnResize={handlers.onResize}
verticalBorder={col => col > 0}
rows={sales?.length ?? 0}
availableColumns={visibleColumns}
emptyText={intl.formatMessage(messages.empty)}
onRowSelectionChange={onSelectSaleIds}
getCellContent={getCellContent}
getCellError={() => false}
selectionActions={() => null}
menuItems={() => []}
onRowClick={handleRowClick}
onHeaderClicked={handleHeaderClick}
rowAnchor={handleRowAnchor}
getColumnTooltipContent={handleGetColumnTooltipContent}
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>
);
};

View file

@ -0,0 +1,114 @@
import { PLACEHOLDER } from "@dashboard/components/Datagrid/const";
import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells";
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
import { Locale } from "@dashboard/components/Locale";
import { formatMoney } from "@dashboard/components/Money";
import { formatPercantage } from "@dashboard/components/Percent/utils";
import { SaleListUrlSortField } from "@dashboard/discounts/urls";
import { SaleFragment } from "@dashboard/graphql";
import { 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 salesListStaticColumnsAdapter = (
intl: IntlShape,
sort: Sort<SaleListUrlSortField>,
) =>
[
{
id: "name",
title: intl.formatMessage(columnsMessages.name),
width: 350,
},
{
id: "startDate",
title: intl.formatMessage(columnsMessages.starts),
width: 200,
},
{
id: "endDate",
title: intl.formatMessage(columnsMessages.ends),
width: 200,
},
{
id: "value",
title: intl.formatMessage(columnsMessages.value),
width: 200,
},
].map(column => ({
...column,
icon: getColumnSortDirectionIcon(sort, column.id),
}));
export const createGetCellContent =
({
sales,
columns,
locale,
selectedChannelId,
}: {
sales: SaleFragment[];
columns: AvailableColumn[];
locale: Locale;
selectedChannelId?: string;
}) =>
([column, row]: Item): GridCell => {
const rowData = sales[row];
const columnId = columns[column]?.id;
const channel = rowData?.channelListings?.find(
lisiting => lisiting.channel.id === selectedChannelId,
);
if (!columnId || !rowData) {
return readonlyTextCell("");
}
switch (columnId) {
case "name":
return readonlyTextCell(rowData.name);
case "startDate":
return readonlyTextCell(
rowData.startDate
? moment(rowData.startDate).locale(locale).format("lll")
: PLACEHOLDER,
);
case "endDate":
return readonlyTextCell(
rowData.endDate
? moment(rowData.endDate).locale(locale).format("lll")
: PLACEHOLDER,
);
case "value":
if (!channel) {
return readonlyTextCell(PLACEHOLDER);
}
if (rowData?.type && channel?.discountValue) {
if (rowData.type === "FIXED") {
return readonlyTextCell(
formatMoney(
{
amount: channel.discountValue,
currency: channel.channel.currencyCode,
},
locale,
),
);
}
return readonlyTextCell(
formatPercantage(channel.discountValue, locale),
);
}
return readonlyTextCell(PLACEHOLDER);
default:
return readonlyTextCell("");
}
};

View file

@ -0,0 +1 @@
export * from "./SaleListDatagrid";

View file

@ -0,0 +1,31 @@
import { defineMessages } from "react-intl";
export const columnsMessages = defineMessages({
name: {
id: "F56hOz",
defaultMessage: "Name",
description: "sale name",
},
starts: {
id: "iBSq6l",
defaultMessage: "Starts",
description: "sale start date",
},
ends: {
id: "giF5UV",
defaultMessage: "Ends",
description: "sale end date",
},
value: {
id: "XZR590",
defaultMessage: "Value",
description: "sale value",
},
});
export const messages = defineMessages({
empty: {
id: "51HE+Q",
defaultMessage: "No sales found",
},
});

View file

@ -1,12 +1,10 @@
// @ts-strict-ignore
import { saleList } from "@dashboard/discounts/fixtures"; import { saleList } from "@dashboard/discounts/fixtures";
import { SaleListUrlSortField } from "@dashboard/discounts/urls"; import { SaleListUrlSortField } from "@dashboard/discounts/urls";
import { import {
filterPageProps, filterPresetsProps,
listActionsProps,
pageListProps, pageListProps,
searchPageProps,
sortPageProps, sortPageProps,
tabPageProps,
} from "@dashboard/fixtures"; } from "@dashboard/fixtures";
import { DiscountStatusEnum, DiscountValueTypeEnum } from "@dashboard/graphql"; import { DiscountStatusEnum, DiscountValueTypeEnum } from "@dashboard/graphql";
import { Meta, StoryObj } from "@storybook/react"; import { Meta, StoryObj } from "@storybook/react";
@ -15,11 +13,18 @@ import { PaginatorContextDecorator } from "../../../../.storybook/decorators";
import SaleListPage, { SaleListPageProps } from "./SaleListPage"; import SaleListPage, { SaleListPageProps } from "./SaleListPage";
const props: SaleListPageProps = { const props: SaleListPageProps = {
...listActionsProps,
...pageListProps.default, ...pageListProps.default,
...filterPageProps, ...searchPageProps,
...sortPageProps, ...sortPageProps,
...tabPageProps, ...filterPresetsProps,
onFilterChange: () => undefined,
selectedSaleIds: [],
onSelectSaleIds: () => {},
onSalesDelete: () => {},
settings: {
...pageListProps.default.settings,
columns: ["name", "startDate", "endDate", "value"],
},
filterOpts: { filterOpts: {
channel: { channel: {
active: false, active: false,
@ -38,8 +43,8 @@ const props: SaleListPageProps = {
started: { started: {
active: false, active: false,
value: { value: {
max: undefined, max: "",
min: undefined, min: "",
}, },
}, },
status: { status: {
@ -76,6 +81,7 @@ export const Loading: Story = {
args: { args: {
...props, ...props,
sales: undefined, sales: undefined,
disabled: true,
}, },
parameters: { parameters: {
chromatic: { diffThreshold: 0.85 }, chromatic: { diffThreshold: 0.85 },

View file

@ -1,25 +1,29 @@
// @ts-strict-ignore import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
import { TopNav } from "@dashboard/components/AppLayout/TopNav"; import { TopNav } from "@dashboard/components/AppLayout/TopNav";
import { Button } from "@dashboard/components/Button"; import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton";
import { getByName } from "@dashboard/components/Filter/utils"; import { getByName } from "@dashboard/components/Filter/utils";
import FilterBar from "@dashboard/components/FilterBar"; import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
import { ListPageLayout } from "@dashboard/components/Layouts"; import { ListPageLayout } from "@dashboard/components/Layouts";
import { saleAddUrl, SaleListUrlSortField } from "@dashboard/discounts/urls"; import {
saleAddUrl,
SaleListUrlSortField,
saleUrl,
} from "@dashboard/discounts/urls";
import { SaleFragment } from "@dashboard/graphql"; import { SaleFragment } from "@dashboard/graphql";
import useNavigator from "@dashboard/hooks/useNavigator";
import { commonMessages } from "@dashboard/intl"; import { commonMessages } from "@dashboard/intl";
import { import {
ChannelProps, ChannelProps,
FilterPageProps, FilterPagePropsWithPresets,
ListActions,
PageListProps, PageListProps,
SortPage, SortPage,
TabPageProps,
} from "@dashboard/types"; } from "@dashboard/types";
import { Card } from "@material-ui/core"; import { Card } from "@material-ui/core";
import React from "react"; import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
import React, { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import SaleList from "../SaleList"; import { SaleListDatagrid } from "../SaleListDatagrid";
import { import {
createFilterStructure, createFilterStructure,
SaleFilterKeys, SaleFilterKeys,
@ -28,70 +32,126 @@ import {
export interface SaleListPageProps export interface SaleListPageProps
extends PageListProps, extends PageListProps,
ListActions, FilterPagePropsWithPresets<SaleFilterKeys, SaleListFilterOpts>,
FilterPageProps<SaleFilterKeys, SaleListFilterOpts>,
SortPage<SaleListUrlSortField>, SortPage<SaleListUrlSortField>,
TabPageProps,
ChannelProps { ChannelProps {
sales: SaleFragment[]; sales: SaleFragment[];
selectedSaleIds: string[];
onSalesDelete: () => void;
onSelectSaleIds: (rows: number[], clearSelection: () => void) => void;
} }
const SaleListPage: React.FC<SaleListPageProps> = ({ const SaleListPage: React.FC<SaleListPageProps> = ({
currentTab,
filterOpts, filterOpts,
initialSearch, initialSearch,
onAll,
onFilterChange, onFilterChange,
onSearchChange, onSearchChange,
onTabChange, onFilterPresetChange,
onTabDelete, onFilterPresetDelete,
onTabSave, onFilterPresetPresetSave,
tabs, onFilterPresetUpdate,
onFilterPresetsAll,
hasPresetsChanged,
onSalesDelete,
filterPresets,
selectedSaleIds,
selectedFilterPreset,
currencySymbol,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const navigation = useNavigator();
const structure = createFilterStructure(intl, filterOpts); const structure = createFilterStructure(intl, filterOpts);
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
const filterDependency = structure.find(getByName("channel")); const filterDependency = structure.find(getByName("channel"));
const handleRowClick = (id: string) => {
navigation(saleUrl(id));
};
return ( return (
<ListPageLayout> <ListPageLayout>
<TopNav title={intl.formatMessage(commonMessages.discounts)}> <TopNav
<Button isAlignToRight={false}
href={saleAddUrl()} withoutBorder
variant="primary" title={intl.formatMessage(commonMessages.discounts)}
data-test-id="create-sale" >
<Box
__flex={1}
display="flex"
justifyContent="space-between"
alignItems="center"
> >
<FormattedMessage <Box display="flex">
id="+MJW+8" <Box marginX={3} display="flex" alignItems="center">
defaultMessage="Create Discount" <ChevronRightIcon />
description="button" </Box>
/>
</Button> <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: "a6GDem",
defaultMessage: "All discounts",
description: "tab name",
})}
/>
</Box>
<Box>
<Button
onClick={() => navigation(saleAddUrl())}
variant="primary"
data-test-id="create-sale"
>
<FormattedMessage
id="+MJW+8"
defaultMessage="Create Discount"
description="button"
/>
</Button>
</Box>
</Box>
</TopNav> </TopNav>
<Card> <Card>
<FilterBar <ListFilters<SaleFilterKeys>
allTabLabel={intl.formatMessage({ currencySymbol={currencySymbol}
id: "c8zJID",
defaultMessage: "All Discounts",
description: "tab name",
})}
currentTab={currentTab}
filterStructure={structure}
initialSearch={initialSearch} initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
id: "lit2zF",
defaultMessage: "Search Discounts",
})}
tabs={tabs}
onAll={onAll}
onFilterChange={onFilterChange} onFilterChange={onFilterChange}
onSearchChange={onSearchChange} onSearchChange={onSearchChange}
onTabChange={onTabChange} filterStructure={structure}
onTabDelete={onTabDelete} searchPlaceholder={intl.formatMessage({
onTabSave={onTabSave} id: "+bhokL",
defaultMessage: "Search discounts...",
})}
actions={
<Box display="flex" gap={4}>
{selectedSaleIds.length > 0 && (
<BulkDeleteButton onClick={onSalesDelete}>
<FormattedMessage
defaultMessage="Delete discounts"
id="Hswqx2"
/>
</BulkDeleteButton>
)}
</Box>
}
/>
<SaleListDatagrid
{...listProps}
hasRowHover={!isFilterPresetOpen}
filterDependency={filterDependency}
onRowClick={handleRowClick}
/> />
<SaleList filterDependency={filterDependency} {...listProps} />
</Card> </Card>
</ListPageLayout> </ListPageLayout>
); );

View file

@ -32,8 +32,8 @@ export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum> &
export type SaleListUrlDialog = "remove" | TabActionDialog; export type SaleListUrlDialog = "remove" | TabActionDialog;
export enum SaleListUrlSortField { export enum SaleListUrlSortField {
name = "name", name = "name",
endDate = "end-date", endDate = "endDate",
startDate = "start-date", startDate = "startDate",
type = "type", type = "type",
value = "value", value = "value",
} }

View file

@ -1,16 +1,14 @@
// @ts-strict-ignore
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 { WindowTitle } from "@dashboard/components/WindowTitle"; import { WindowTitle } from "@dashboard/components/WindowTitle";
import { import {
SaleFragment,
useSaleBulkDeleteMutation, useSaleBulkDeleteMutation,
useSaleListQuery, useSaleListQuery,
} 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";
@ -19,8 +17,8 @@ import usePaginator, {
createPaginationState, createPaginationState,
PaginatorContext, PaginatorContext,
} from "@dashboard/hooks/usePaginator"; } from "@dashboard/hooks/usePaginator";
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
import { commonMessages } from "@dashboard/intl"; import { commonMessages } 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";
@ -28,8 +26,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, { useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import SaleListPage from "../../components/SaleListPage"; import SaleListPage from "../../components/SaleListPage";
@ -39,14 +37,10 @@ import {
SaleListUrlQueryParams, SaleListUrlQueryParams,
} from "../../urls"; } from "../../urls";
import { import {
deleteFilterTab,
getActiveFilters,
getFilterOpts, getFilterOpts,
getFilterQueryParam, getFilterQueryParam,
getFiltersCurrentTab,
getFilterTabs,
getFilterVariables, getFilterVariables,
saveFilterTab, storageUtils,
} from "./filters"; } from "./filters";
import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort"; import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort";
@ -57,9 +51,6 @@ interface SaleListProps {
export const SaleList: React.FC<SaleListProps> = ({ params }) => { export const SaleList: React.FC<SaleListProps> = ({ params }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids,
);
const { updateListSettings, settings } = useListSettings( const { updateListSettings, settings } = useListSettings(
ListViews.SALES_LIST, ListViews.SALES_LIST,
); );
@ -73,7 +64,7 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
); );
const channelOpts = availableChannels const channelOpts = availableChannels
? mapNodeToChoice(availableChannels, channel => channel.slug) ? mapNodeToChoice(availableChannels, channel => channel.slug)
: null; : [];
const [openModal, closeModal] = createDialogActionHandlers< const [openModal, closeModal] = createDialogActionHandlers<
SaleListUrlDialog, SaleListUrlDialog,
@ -95,21 +86,44 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
variables: queryVariables, variables: queryVariables,
}); });
const tabs = getFilterTabs(); const sales: SaleFragment[] = mapEdgesToItems(data?.sales) ?? [];
const currentTab = getFiltersCurrentTab(params, tabs); const {
clearRowSelection,
selectedRowIds,
setSelectedRowIds,
setClearDatagridRowSelectionCallback,
} = useRowSelection(params);
const {
hasPresetsChange,
onPresetChange,
onPresetDelete,
onPresetSave,
onPresetUpdate,
presetIdToDelete,
selectedPreset,
presets,
setPresetIdToDelete,
} = useFilterPresets({
getUrl: saleListUrl,
params,
storageUtils,
reset: clearRowSelection,
});
const [changeFilters, resetFilters, handleSearchChange] = const [changeFilters, resetFilters, handleSearchChange] =
createFilterHandlers({ createFilterHandlers({
cleanupFn: reset, cleanupFn: clearRowSelection,
createUrl: saleListUrl, createUrl: saleListUrl,
getFilterQueryParam, getFilterQueryParam,
navigate, navigate,
params, params,
keepActiveTab: true,
}); });
useEffect(() => { useEffect(() => {
if (!canBeSorted(params.sort, !!selectedChannel)) { if (!canBeSorted(params?.sort, !!selectedChannel)) {
navigate( navigate(
saleListUrl({ saleListUrl({
...params, ...params,
@ -119,43 +133,20 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
} }
}, [params]); }, [params]);
const handleTabChange = (tab: number) => {
reset();
navigate(
saleListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data,
}),
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(saleListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
const paginationValues = usePaginator({ const paginationValues = usePaginator({
pageInfo: maybe(() => data.sales.pageInfo), pageInfo: data?.sales?.pageInfo,
paginationState, paginationState,
queryString: params, queryString: params,
}); });
const [saleBulkDelete, saleBulkDeleteOpts] = useSaleBulkDeleteMutation({ const [saleBulkDelete, saleBulkDeleteOpts] = useSaleBulkDeleteMutation({
onCompleted: data => { onCompleted: data => {
if (data.saleBulkDelete.errors.length === 0) { if (data?.saleBulkDelete?.errors?.length === 0) {
notify({ notify({
status: "success", status: "success",
text: intl.formatMessage(commonMessages.savedChanges), text: intl.formatMessage(commonMessages.savedChanges),
}); });
reset(); clearRowSelection();
closeModal(); closeModal();
refetch(); refetch();
} }
@ -164,57 +155,82 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
const handleSort = createSortHandler(navigate, saleListUrl, params); const handleSort = createSortHandler(navigate, saleListUrl, params);
const onSaleBulkDelete = () => const handleSelectSaleIds = useCallback(
saleBulkDelete({ (rows: number[], clearSelection: () => void) => {
if (!sales) {
return;
}
const rowsIds = rows.map(row => sales[row].id);
const haveSaveValues = isEqual(rowsIds, selectedRowIds);
if (!haveSaveValues) {
setSelectedRowIds(rowsIds);
}
setClearDatagridRowSelectionCallback(clearSelection);
},
[
sales,
selectedRowIds,
setClearDatagridRowSelectionCallback,
setSelectedRowIds,
],
);
const getFilterPresetDeleteName = () => {
if (!presetIdToDelete || !presets[presetIdToDelete - 1]) {
return "...";
}
return presets[presetIdToDelete - 1].name;
};
const onSaleBulkDelete = async () => {
await saleBulkDelete({
variables: { variables: {
ids: params.ids, ids: selectedRowIds,
}, },
}); });
clearRowSelection();
};
return ( return (
<PaginatorContext.Provider value={paginationValues}> <PaginatorContext.Provider value={paginationValues}>
<WindowTitle title={intl.formatMessage(commonMessages.discounts)} /> <WindowTitle title={intl.formatMessage(commonMessages.discounts)} />
<SaleListPage <SaleListPage
currentTab={currentTab} currencySymbol={selectedChannel?.currencyCode}
onSelectSaleIds={handleSelectSaleIds}
filterOpts={getFilterOpts(params, channelOpts)} filterOpts={getFilterOpts(params, channelOpts)}
initialSearch={params.query || ""} initialSearch={params.query || ""}
onSearchChange={handleSearchChange} onSearchChange={handleSearchChange}
onFilterChange={filter => changeFilters(filter)} onFilterChange={filter => changeFilters(filter)}
onAll={resetFilters} onFilterPresetDelete={(id: number) => {
onTabChange={handleTabChange} setPresetIdToDelete(id);
onTabDelete={() => openModal("delete-search")} openModal("delete-search");
onTabSave={() => openModal("save-search")} }}
tabs={tabs.map(tab => tab.name)} onFilterPresetPresetSave={() => openModal("save-search")}
sales={mapEdgesToItems(data?.sales)} onFilterPresetChange={onPresetChange}
onFilterPresetUpdate={onPresetUpdate}
onFilterPresetsAll={resetFilters}
filterPresets={presets.map(preset => preset.name)}
selectedFilterPreset={selectedPreset}
hasPresetsChanged={hasPresetsChange}
onSalesDelete={() => openModal("remove")}
selectedSaleIds={selectedRowIds}
sales={sales}
settings={settings} settings={settings}
disabled={loading} disabled={loading}
onSort={handleSort} onSort={handleSort}
onUpdateListSettings={updateListSettings} onUpdateListSettings={updateListSettings}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)} sort={getSortParams(params)}
toggle={toggle} selectedChannelId={selectedChannel?.id ?? ""}
toggleAll={toggleAll}
toolbar={
<IconButton
variant="secondary"
color="primary"
onClick={() =>
openModal("remove", {
ids: listElements,
})
}
>
<DeleteIcon />
</IconButton>
}
selectedChannelId={selectedChannel?.id}
/> />
<ActionDialog <ActionDialog
confirmButtonState={saleBulkDeleteOpts.status} confirmButtonState={saleBulkDeleteOpts.status}
onClose={closeModal} onClose={closeModal}
onConfirm={onSaleBulkDelete} onConfirm={onSaleBulkDelete}
open={params.action === "remove" && canOpenBulkActionDialog} open={params.action === "remove" && selectedRowIds.length > 0}
title={intl.formatMessage({ title={intl.formatMessage({
id: "ZWIjvr", id: "ZWIjvr",
defaultMessage: "Delete Sales", defaultMessage: "Delete Sales",
@ -222,32 +238,30 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
})} })}
variant="delete" variant="delete"
> >
{canOpenBulkActionDialog && ( <DialogContentText>
<DialogContentText> <FormattedMessage
<FormattedMessage id="FPzzh7"
id="FPzzh7" defaultMessage="{counter,plural,one{Are you sure you want to delete this sale?} other{Are you sure you want to delete {displayQuantity} sales?}}"
defaultMessage="{counter,plural,one{Are you sure you want to delete this sale?} other{Are you sure you want to delete {displayQuantity} sales?}}" description="dialog content"
description="dialog content" values={{
values={{ counter: selectedRowIds.length,
counter: params.ids.length, displayQuantity: <strong>{selectedRowIds.length}</strong>,
displayQuantity: <strong>{params.ids.length}</strong>, }}
}} />
/> </DialogContentText>
</DialogContentText>
)}
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog <SaveFilterTabDialog
open={params.action === "save-search"} open={params.action === "save-search"}
confirmButtonState="default" confirmButtonState="default"
onClose={closeModal} onClose={closeModal}
onSubmit={handleTabSave} onSubmit={onPresetSave}
/> />
<DeleteFilterTabDialog <DeleteFilterTabDialog
open={params.action === "delete-search"} open={params.action === "delete-search"}
confirmButtonState="default" confirmButtonState="default"
onClose={closeModal} onClose={closeModal}
onSubmit={handleTabDelete} onSubmit={onPresetDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")} tabName={getFilterPresetDeleteName()}
/> />
</PaginatorContext.Provider> </PaginatorContext.Provider>
); );

View file

@ -121,8 +121,7 @@ export function getFilterQueryParam(
} }
} }
export const { deleteFilterTab, getFilterTabs, saveFilterTab } = export const storageUtils = createFilterTabUtils<string>(SALE_FILTERS_KEY);
createFilterTabUtils<SaleListUrlFilters>(SALE_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
createFilterUtils<SaleListUrlQueryParams, SaleListUrlFilters>({ createFilterUtils<SaleListUrlQueryParams, SaleListUrlFilters>({

View file

@ -6,9 +6,13 @@ import { createGetSortQueryVariables } from "@dashboard/utils/sort";
export const DEFAULT_SORT_KEY = SaleListUrlSortField.name; export const DEFAULT_SORT_KEY = SaleListUrlSortField.name;
export function canBeSorted( export function canBeSorted(
sort: SaleListUrlSortField, sort: SaleListUrlSortField | undefined,
isChannelSelected: boolean, isChannelSelected: boolean,
) { ) {
if (sort === undefined) {
return false;
}
switch (sort) { switch (sort) {
case SaleListUrlSortField.name: case SaleListUrlSortField.name:
case SaleListUrlSortField.startDate: case SaleListUrlSortField.startDate:

View file

@ -314,6 +314,7 @@ export const filterPresetsProps: FilterPresetsProps = {
onFilterPresetPresetSave: () => undefined, onFilterPresetPresetSave: () => undefined,
onFilterPresetUpdate: () => undefined, onFilterPresetUpdate: () => undefined,
filterPresets: ["Tab X"], filterPresets: ["Tab X"],
hasPresetsChanged: () => false,
}; };
export const paginatorContextValues: PaginatorContextValues = { export const paginatorContextValues: PaginatorContextValues = {

View file

@ -9,10 +9,8 @@ import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
export interface OrderDraftListHeaderProps export interface OrderDraftListHeaderProps extends FilterPresetsProps {
extends Omit<FilterPresetsProps, "onTabDelete"> {
limits: RefreshLimitsQuery["shop"]["limits"]; limits: RefreshLimitsQuery["shop"]["limits"];
hasPresetsChanged: boolean;
isFilterPresetOpen: boolean; isFilterPresetOpen: boolean;
disabled: boolean; disabled: boolean;
onAdd: () => void; onAdd: () => void;
@ -55,7 +53,7 @@ export const OrderDraftListHeader = ({
</Box> </Box>
<FilterPresetsSelect <FilterPresetsSelect
presetsChanged={hasPresetsChanged} presetsChanged={hasPresetsChanged()}
onSelect={onFilterPresetChange} onSelect={onFilterPresetChange}
onRemove={onFilterPresetDelete} onRemove={onFilterPresetDelete}
onUpdate={onFilterPresetUpdate} onUpdate={onFilterPresetUpdate}

View file

@ -69,7 +69,7 @@ const OrderDraftListPage: React.FC<OrderDraftListPageProps> = ({
<OrderDraftListHeader <OrderDraftListHeader
disabled={disabled} disabled={disabled}
selectedFilterPreset={selectedFilterPreset} selectedFilterPreset={selectedFilterPreset}
hasPresetsChanged={hasPresetsChanged()} hasPresetsChanged={hasPresetsChanged}
isFilterPresetOpen={isFilterPresetOpen} isFilterPresetOpen={isFilterPresetOpen}
setFilterPresetOpen={setFilterPresetOpen} setFilterPresetOpen={setFilterPresetOpen}
limits={limits} limits={limits}

View file

@ -123,13 +123,14 @@ export interface FilterProps<TKeys extends string> {
} }
export interface FilterPresetsProps { export interface FilterPresetsProps {
selectedFilterPreset: number; selectedFilterPreset: number | undefined;
filterPresets: string[]; filterPresets: string[];
onFilterPresetsAll: () => void; onFilterPresetsAll: () => void;
onFilterPresetChange: (id: number) => void; onFilterPresetChange: (id: number) => void;
onFilterPresetUpdate: (name: string) => void; onFilterPresetUpdate: (name: string) => void;
onFilterPresetDelete: (id: number) => void; onFilterPresetDelete: (id: number) => void;
onFilterPresetPresetSave: () => void; onFilterPresetPresetSave: () => void;
hasPresetsChanged: () => boolean;
} }
export interface TabPageProps { export interface TabPageProps {