Introduce datagrid on Discounts list (#3939)
Co-authored-by: wojteknowacki <wojciech.nowacki@saleor.io>
This commit is contained in:
parent
33b4199cec
commit
52f58eb00a
27 changed files with 622 additions and 462 deletions
5
.changeset/polite-trainers-fix.md
Normal file
5
.changeset/polite-trainers-fix.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduct datagrid on discounts list page
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 won’t be able to assign them to created pages."
|
"string": "Are you sure you want to delete selected page types? If you remove them you won’t be able to assign them to created pages."
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>>({});
|
||||||
|
|
1
src/components/Datagrid/const.ts
Normal file
1
src/components/Datagrid/const.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const PLACEHOLDER = "-";
|
|
@ -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";
|
||||||
|
|
17
src/components/Percent/utils.test.ts
Normal file
17
src/components/Percent/utils.test.ts
Normal 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%");
|
||||||
|
});
|
||||||
|
});
|
13
src/components/Percent/utils.ts
Normal file
13
src/components/Percent/utils.ts
Normal 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",
|
||||||
|
})
|
||||||
|
: "-";
|
||||||
|
};
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./SaleList";
|
|
||||||
export * from "./SaleList";
|
|
175
src/discounts/components/SaleListDatagrid/SaleListDatagrid.tsx
Normal file
175
src/discounts/components/SaleListDatagrid/SaleListDatagrid.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
114
src/discounts/components/SaleListDatagrid/datagrid.ts
Normal file
114
src/discounts/components/SaleListDatagrid/datagrid.ts
Normal 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("");
|
||||||
|
}
|
||||||
|
};
|
1
src/discounts/components/SaleListDatagrid/index.tsx
Normal file
1
src/discounts/components/SaleListDatagrid/index.tsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./SaleListDatagrid";
|
31
src/discounts/components/SaleListDatagrid/messages.ts
Normal file
31
src/discounts/components/SaleListDatagrid/messages.ts
Normal 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",
|
||||||
|
},
|
||||||
|
});
|
|
@ -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 },
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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",
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>({
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue