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()
|
||||
.loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount)
|
||||
*/
|
||||
cy.visit(urlList.sales)
|
||||
.expectSkeletonIsVisible()
|
||||
.waitForProgressBarToNotExist();
|
||||
cy.visit(urlList.sales);
|
||||
createSale({
|
||||
saleName,
|
||||
channelName: channel.name,
|
||||
|
|
|
@ -24,7 +24,6 @@ export function createSale({
|
|||
|
||||
cy.get(SALES_SELECTORS.createSaleButton)
|
||||
.click()
|
||||
.waitForProgressBarToNotBeVisible()
|
||||
.get(SALES_SELECTORS.nameInput)
|
||||
.type(saleName)
|
||||
.get(discountOption)
|
||||
|
@ -37,7 +36,6 @@ export function createSale({
|
|||
.addAliasToGraphRequest("SaleCreate")
|
||||
.get(SALES_SELECTORS.saveButton)
|
||||
.click()
|
||||
.confirmationMessageShouldDisappear()
|
||||
.waitForRequestAndCheckIfNoErrors("@SaleCreate");
|
||||
}
|
||||
|
||||
|
@ -102,9 +100,7 @@ export function createSaleWithNewProduct({
|
|||
cy.clearSessionData()
|
||||
.loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount)
|
||||
*/
|
||||
cy.visit(urlList.sales)
|
||||
.expectSkeletonIsVisible()
|
||||
.waitForProgressBarToNotExist();
|
||||
cy.visit(urlList.sales);
|
||||
createSale({
|
||||
saleName: name,
|
||||
channelName: channel.name,
|
||||
|
@ -141,9 +137,7 @@ export function createSaleWithNewVariant({
|
|||
cy.clearSessionData()
|
||||
.loginUserViaRequest("auth", ONE_PERMISSION_USERS.discount)
|
||||
*/
|
||||
cy.visit(urlList.sales)
|
||||
.expectSkeletonIsVisible()
|
||||
.waitForProgressBarToNotExist();
|
||||
cy.visit(urlList.sales);
|
||||
createSale({
|
||||
saleName: name,
|
||||
channelName: channel.name,
|
||||
|
|
|
@ -96,6 +96,9 @@
|
|||
"context": "button",
|
||||
"string": "Activate"
|
||||
},
|
||||
"+bhokL": {
|
||||
"string": "Search discounts..."
|
||||
},
|
||||
"+do3gl": {
|
||||
"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."
|
||||
|
@ -2758,6 +2761,9 @@
|
|||
"context": "create gift card product alert message",
|
||||
"string": "Create a gift card product"
|
||||
},
|
||||
"Hswqx2": {
|
||||
"string": "Delete discounts"
|
||||
},
|
||||
"HvJPcU": {
|
||||
"string": "Category deleted"
|
||||
},
|
||||
|
@ -5317,6 +5323,10 @@
|
|||
"a5msuh": {
|
||||
"string": "Yes"
|
||||
},
|
||||
"a6GDem": {
|
||||
"context": "tab name",
|
||||
"string": "All discounts"
|
||||
},
|
||||
"a9S9Je": {
|
||||
"context": "page types section name",
|
||||
"string": "Page Types"
|
||||
|
@ -5621,10 +5631,6 @@
|
|||
"c8nvms": {
|
||||
"string": "Sales"
|
||||
},
|
||||
"c8zJID": {
|
||||
"context": "tab name",
|
||||
"string": "All Discounts"
|
||||
},
|
||||
"cBHRxx": {
|
||||
"context": "button",
|
||||
"string": "Assign Warehouse"
|
||||
|
@ -6828,9 +6834,6 @@
|
|||
"context": "export items as csv file",
|
||||
"string": "Plain CSV file"
|
||||
},
|
||||
"lit2zF": {
|
||||
"string": "Search Discounts"
|
||||
},
|
||||
"ll2dE6": {
|
||||
"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."
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface ListFiltersProps<TKeys extends string = string>
|
|||
actions?: ReactNode;
|
||||
}
|
||||
|
||||
export const ListFilters = ({
|
||||
export const ListFilters = <TFilterKeys extends string = string>({
|
||||
currencySymbol,
|
||||
filterStructure,
|
||||
initialSearch,
|
||||
|
@ -27,7 +27,7 @@ export const ListFilters = ({
|
|||
onFilterAttributeFocus,
|
||||
errorMessages,
|
||||
actions,
|
||||
}: ListFiltersProps) => {
|
||||
}: ListFiltersProps<TFilterKeys>) => {
|
||||
const isProductPage = window.location.pathname.includes("/products");
|
||||
const productListingPageFiltersFlag = useFlag("product_filters");
|
||||
const filtersEnabled = isProductPage && productListingPageFiltersFlag.enabled;
|
||||
|
@ -45,7 +45,7 @@ export const ListFilters = ({
|
|||
{filtersEnabled ? (
|
||||
<ExpressionFilters />
|
||||
) : (
|
||||
<FiltersSelect
|
||||
<FiltersSelect<TFilterKeys>
|
||||
errorMessages={errorMessages}
|
||||
menu={filterStructure}
|
||||
currencySymbol={currencySymbol}
|
||||
|
|
|
@ -19,17 +19,17 @@ export interface FilterProps<TFilterKeys extends string = string> {
|
|||
currencySymbol?: string;
|
||||
errorMessages?: FilterErrorMessages<TFilterKeys>;
|
||||
menu: IFilter<TFilterKeys>;
|
||||
onFilterAdd: (filter: Array<FilterElement<string>>) => void;
|
||||
onFilterAdd: (filter: Array<FilterElement<TFilterKeys>>) => void;
|
||||
onFilterAttributeFocus?: (id?: string) => void;
|
||||
}
|
||||
|
||||
export const FiltersSelect = ({
|
||||
export const FiltersSelect = <TFilterKeys extends string = string>({
|
||||
currencySymbol,
|
||||
menu,
|
||||
onFilterAdd,
|
||||
onFilterAttributeFocus,
|
||||
errorMessages,
|
||||
}: FilterProps) => {
|
||||
}: FilterProps<TFilterKeys>) => {
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const [isFilterMenuOpened, setFilterMenuOpened] = useState(false);
|
||||
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 { LocaleConsumer } from "../Locale";
|
||||
import { formatPercantage } from "./utils";
|
||||
|
||||
interface PercentProps {
|
||||
amount: number;
|
||||
|
@ -8,14 +9,7 @@ interface PercentProps {
|
|||
|
||||
const Percent: React.FC<PercentProps> = ({ amount }) => (
|
||||
<LocaleConsumer>
|
||||
{({ locale }) =>
|
||||
amount
|
||||
? (amount / 100).toLocaleString(locale, {
|
||||
maximumFractionDigits: 2,
|
||||
style: "percent",
|
||||
})
|
||||
: "-"
|
||||
}
|
||||
{({ locale }) => formatPercantage(amount, locale)}
|
||||
</LocaleConsumer>
|
||||
);
|
||||
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]: {
|
||||
rowNumber: PAGINATE_BY,
|
||||
columns: ["name", "startDate", "endDate", "value"],
|
||||
},
|
||||
[ListViews.SHIPPING_METHODS_LIST]: {
|
||||
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 { SaleListUrlSortField } from "@dashboard/discounts/urls";
|
||||
import {
|
||||
filterPageProps,
|
||||
listActionsProps,
|
||||
filterPresetsProps,
|
||||
pageListProps,
|
||||
searchPageProps,
|
||||
sortPageProps,
|
||||
tabPageProps,
|
||||
} from "@dashboard/fixtures";
|
||||
import { DiscountStatusEnum, DiscountValueTypeEnum } from "@dashboard/graphql";
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
|
@ -15,11 +13,18 @@ import { PaginatorContextDecorator } from "../../../../.storybook/decorators";
|
|||
import SaleListPage, { SaleListPageProps } from "./SaleListPage";
|
||||
|
||||
const props: SaleListPageProps = {
|
||||
...listActionsProps,
|
||||
...pageListProps.default,
|
||||
...filterPageProps,
|
||||
...searchPageProps,
|
||||
...sortPageProps,
|
||||
...tabPageProps,
|
||||
...filterPresetsProps,
|
||||
onFilterChange: () => undefined,
|
||||
selectedSaleIds: [],
|
||||
onSelectSaleIds: () => {},
|
||||
onSalesDelete: () => {},
|
||||
settings: {
|
||||
...pageListProps.default.settings,
|
||||
columns: ["name", "startDate", "endDate", "value"],
|
||||
},
|
||||
filterOpts: {
|
||||
channel: {
|
||||
active: false,
|
||||
|
@ -38,8 +43,8 @@ const props: SaleListPageProps = {
|
|||
started: {
|
||||
active: false,
|
||||
value: {
|
||||
max: undefined,
|
||||
min: undefined,
|
||||
max: "",
|
||||
min: "",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
|
@ -76,6 +81,7 @@ export const Loading: Story = {
|
|||
args: {
|
||||
...props,
|
||||
sales: undefined,
|
||||
disabled: true,
|
||||
},
|
||||
parameters: {
|
||||
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 { Button } from "@dashboard/components/Button";
|
||||
import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton";
|
||||
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 { saleAddUrl, SaleListUrlSortField } from "@dashboard/discounts/urls";
|
||||
import {
|
||||
saleAddUrl,
|
||||
SaleListUrlSortField,
|
||||
saleUrl,
|
||||
} from "@dashboard/discounts/urls";
|
||||
import { SaleFragment } from "@dashboard/graphql";
|
||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
import { commonMessages } from "@dashboard/intl";
|
||||
import {
|
||||
ChannelProps,
|
||||
FilterPageProps,
|
||||
ListActions,
|
||||
FilterPagePropsWithPresets,
|
||||
PageListProps,
|
||||
SortPage,
|
||||
TabPageProps,
|
||||
} from "@dashboard/types";
|
||||
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 SaleList from "../SaleList";
|
||||
import { SaleListDatagrid } from "../SaleListDatagrid";
|
||||
import {
|
||||
createFilterStructure,
|
||||
SaleFilterKeys,
|
||||
|
@ -28,37 +32,82 @@ import {
|
|||
|
||||
export interface SaleListPageProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
FilterPageProps<SaleFilterKeys, SaleListFilterOpts>,
|
||||
FilterPagePropsWithPresets<SaleFilterKeys, SaleListFilterOpts>,
|
||||
SortPage<SaleListUrlSortField>,
|
||||
TabPageProps,
|
||||
ChannelProps {
|
||||
sales: SaleFragment[];
|
||||
selectedSaleIds: string[];
|
||||
onSalesDelete: () => void;
|
||||
onSelectSaleIds: (rows: number[], clearSelection: () => void) => void;
|
||||
}
|
||||
|
||||
const SaleListPage: React.FC<SaleListPageProps> = ({
|
||||
currentTab,
|
||||
filterOpts,
|
||||
initialSearch,
|
||||
onAll,
|
||||
onFilterChange,
|
||||
onSearchChange,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
onTabSave,
|
||||
tabs,
|
||||
onFilterPresetChange,
|
||||
onFilterPresetDelete,
|
||||
onFilterPresetPresetSave,
|
||||
onFilterPresetUpdate,
|
||||
onFilterPresetsAll,
|
||||
hasPresetsChanged,
|
||||
onSalesDelete,
|
||||
filterPresets,
|
||||
selectedSaleIds,
|
||||
selectedFilterPreset,
|
||||
currencySymbol,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const navigation = useNavigator();
|
||||
const structure = createFilterStructure(intl, filterOpts);
|
||||
|
||||
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||
const filterDependency = structure.find(getByName("channel"));
|
||||
|
||||
const handleRowClick = (id: string) => {
|
||||
navigation(saleUrl(id));
|
||||
};
|
||||
|
||||
return (
|
||||
<ListPageLayout>
|
||||
<TopNav title={intl.formatMessage(commonMessages.discounts)}>
|
||||
<TopNav
|
||||
isAlignToRight={false}
|
||||
withoutBorder
|
||||
title={intl.formatMessage(commonMessages.discounts)}
|
||||
>
|
||||
<Box
|
||||
__flex={1}
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box display="flex">
|
||||
<Box marginX={3} display="flex" alignItems="center">
|
||||
<ChevronRightIcon />
|
||||
</Box>
|
||||
|
||||
<FilterPresetsSelect
|
||||
presetsChanged={hasPresetsChanged()}
|
||||
onSelect={onFilterPresetChange}
|
||||
onRemove={onFilterPresetDelete}
|
||||
onUpdate={onFilterPresetUpdate}
|
||||
savedPresets={filterPresets}
|
||||
activePreset={selectedFilterPreset}
|
||||
onSelectAll={onFilterPresetsAll}
|
||||
onSave={onFilterPresetPresetSave}
|
||||
isOpen={isFilterPresetOpen}
|
||||
onOpenChange={setFilterPresetOpen}
|
||||
selectAllLabel={intl.formatMessage({
|
||||
id: "a6GDem",
|
||||
defaultMessage: "All discounts",
|
||||
description: "tab name",
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
href={saleAddUrl()}
|
||||
onClick={() => navigation(saleAddUrl())}
|
||||
variant="primary"
|
||||
data-test-id="create-sale"
|
||||
>
|
||||
|
@ -68,30 +117,41 @@ const SaleListPage: React.FC<SaleListPageProps> = ({
|
|||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</TopNav>
|
||||
|
||||
<Card>
|
||||
<FilterBar
|
||||
allTabLabel={intl.formatMessage({
|
||||
id: "c8zJID",
|
||||
defaultMessage: "All Discounts",
|
||||
description: "tab name",
|
||||
})}
|
||||
currentTab={currentTab}
|
||||
filterStructure={structure}
|
||||
<ListFilters<SaleFilterKeys>
|
||||
currencySymbol={currencySymbol}
|
||||
initialSearch={initialSearch}
|
||||
searchPlaceholder={intl.formatMessage({
|
||||
id: "lit2zF",
|
||||
defaultMessage: "Search Discounts",
|
||||
})}
|
||||
tabs={tabs}
|
||||
onAll={onAll}
|
||||
onFilterChange={onFilterChange}
|
||||
onSearchChange={onSearchChange}
|
||||
onTabChange={onTabChange}
|
||||
onTabDelete={onTabDelete}
|
||||
onTabSave={onTabSave}
|
||||
filterStructure={structure}
|
||||
searchPlaceholder={intl.formatMessage({
|
||||
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>
|
||||
</ListPageLayout>
|
||||
);
|
||||
|
|
|
@ -32,8 +32,8 @@ export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum> &
|
|||
export type SaleListUrlDialog = "remove" | TabActionDialog;
|
||||
export enum SaleListUrlSortField {
|
||||
name = "name",
|
||||
endDate = "end-date",
|
||||
startDate = "start-date",
|
||||
endDate = "endDate",
|
||||
startDate = "startDate",
|
||||
type = "type",
|
||||
value = "value",
|
||||
}
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
// @ts-strict-ignore
|
||||
import ActionDialog from "@dashboard/components/ActionDialog";
|
||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
||||
import SaveFilterTabDialog, {
|
||||
SaveFilterTabDialogFormData,
|
||||
} from "@dashboard/components/SaveFilterTabDialog";
|
||||
import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog";
|
||||
import { WindowTitle } from "@dashboard/components/WindowTitle";
|
||||
import {
|
||||
SaleFragment,
|
||||
useSaleBulkDeleteMutation,
|
||||
useSaleListQuery,
|
||||
} from "@dashboard/graphql";
|
||||
import useBulkActions from "@dashboard/hooks/useBulkActions";
|
||||
import { useFilterPresets } from "@dashboard/hooks/useFilterPresets";
|
||||
import useListSettings from "@dashboard/hooks/useListSettings";
|
||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
import useNotifier from "@dashboard/hooks/useNotifier";
|
||||
|
@ -19,8 +17,8 @@ import usePaginator, {
|
|||
createPaginationState,
|
||||
PaginatorContext,
|
||||
} from "@dashboard/hooks/usePaginator";
|
||||
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
|
||||
import { commonMessages } from "@dashboard/intl";
|
||||
import { maybe } from "@dashboard/misc";
|
||||
import { ListViews } from "@dashboard/types";
|
||||
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
||||
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 { getSortParams } from "@dashboard/utils/sort";
|
||||
import { DialogContentText } from "@material-ui/core";
|
||||
import { DeleteIcon, IconButton } from "@saleor/macaw-ui";
|
||||
import React, { useEffect } from "react";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import SaleListPage from "../../components/SaleListPage";
|
||||
|
@ -39,14 +37,10 @@ import {
|
|||
SaleListUrlQueryParams,
|
||||
} from "../../urls";
|
||||
import {
|
||||
deleteFilterTab,
|
||||
getActiveFilters,
|
||||
getFilterOpts,
|
||||
getFilterQueryParam,
|
||||
getFiltersCurrentTab,
|
||||
getFilterTabs,
|
||||
getFilterVariables,
|
||||
saveFilterTab,
|
||||
storageUtils,
|
||||
} from "./filters";
|
||||
import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort";
|
||||
|
||||
|
@ -57,9 +51,6 @@ interface SaleListProps {
|
|||
export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids,
|
||||
);
|
||||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.SALES_LIST,
|
||||
);
|
||||
|
@ -73,7 +64,7 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
);
|
||||
const channelOpts = availableChannels
|
||||
? mapNodeToChoice(availableChannels, channel => channel.slug)
|
||||
: null;
|
||||
: [];
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
SaleListUrlDialog,
|
||||
|
@ -95,21 +86,44 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
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] =
|
||||
createFilterHandlers({
|
||||
cleanupFn: reset,
|
||||
cleanupFn: clearRowSelection,
|
||||
createUrl: saleListUrl,
|
||||
getFilterQueryParam,
|
||||
navigate,
|
||||
params,
|
||||
keepActiveTab: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!canBeSorted(params.sort, !!selectedChannel)) {
|
||||
if (!canBeSorted(params?.sort, !!selectedChannel)) {
|
||||
navigate(
|
||||
saleListUrl({
|
||||
...params,
|
||||
|
@ -119,43 +133,20 @@ export const SaleList: React.FC<SaleListProps> = ({ 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({
|
||||
pageInfo: maybe(() => data.sales.pageInfo),
|
||||
pageInfo: data?.sales?.pageInfo,
|
||||
paginationState,
|
||||
queryString: params,
|
||||
});
|
||||
|
||||
const [saleBulkDelete, saleBulkDeleteOpts] = useSaleBulkDeleteMutation({
|
||||
onCompleted: data => {
|
||||
if (data.saleBulkDelete.errors.length === 0) {
|
||||
if (data?.saleBulkDelete?.errors?.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges),
|
||||
});
|
||||
reset();
|
||||
clearRowSelection();
|
||||
closeModal();
|
||||
refetch();
|
||||
}
|
||||
|
@ -164,57 +155,82 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
|
||||
const handleSort = createSortHandler(navigate, saleListUrl, params);
|
||||
|
||||
const onSaleBulkDelete = () =>
|
||||
saleBulkDelete({
|
||||
const handleSelectSaleIds = useCallback(
|
||||
(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: {
|
||||
ids: params.ids,
|
||||
ids: selectedRowIds,
|
||||
},
|
||||
});
|
||||
clearRowSelection();
|
||||
};
|
||||
|
||||
return (
|
||||
<PaginatorContext.Provider value={paginationValues}>
|
||||
<WindowTitle title={intl.formatMessage(commonMessages.discounts)} />
|
||||
<SaleListPage
|
||||
currentTab={currentTab}
|
||||
currencySymbol={selectedChannel?.currencyCode}
|
||||
onSelectSaleIds={handleSelectSaleIds}
|
||||
filterOpts={getFilterOpts(params, channelOpts)}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={handleSearchChange}
|
||||
onFilterChange={filter => changeFilters(filter)}
|
||||
onAll={resetFilters}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
sales={mapEdgesToItems(data?.sales)}
|
||||
onFilterPresetDelete={(id: number) => {
|
||||
setPresetIdToDelete(id);
|
||||
openModal("delete-search");
|
||||
}}
|
||||
onFilterPresetPresetSave={() => openModal("save-search")}
|
||||
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}
|
||||
disabled={loading}
|
||||
onSort={handleSort}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
sort={getSortParams(params)}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("remove", {
|
||||
ids: listElements,
|
||||
})
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
selectedChannelId={selectedChannel?.id}
|
||||
selectedChannelId={selectedChannel?.id ?? ""}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={saleBulkDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={onSaleBulkDelete}
|
||||
open={params.action === "remove" && canOpenBulkActionDialog}
|
||||
open={params.action === "remove" && selectedRowIds.length > 0}
|
||||
title={intl.formatMessage({
|
||||
id: "ZWIjvr",
|
||||
defaultMessage: "Delete Sales",
|
||||
|
@ -222,32 +238,30 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
})}
|
||||
variant="delete"
|
||||
>
|
||||
{canOpenBulkActionDialog && (
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
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?}}"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: params.ids.length,
|
||||
displayQuantity: <strong>{params.ids.length}</strong>,
|
||||
counter: selectedRowIds.length,
|
||||
displayQuantity: <strong>{selectedRowIds.length}</strong>,
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
)}
|
||||
</ActionDialog>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleTabSave}
|
||||
onSubmit={onPresetSave}
|
||||
/>
|
||||
<DeleteFilterTabDialog
|
||||
open={params.action === "delete-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleTabDelete}
|
||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
onSubmit={onPresetDelete}
|
||||
tabName={getFilterPresetDeleteName()}
|
||||
/>
|
||||
</PaginatorContext.Provider>
|
||||
);
|
||||
|
|
|
@ -121,8 +121,7 @@ export function getFilterQueryParam(
|
|||
}
|
||||
}
|
||||
|
||||
export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
|
||||
createFilterTabUtils<SaleListUrlFilters>(SALE_FILTERS_KEY);
|
||||
export const storageUtils = createFilterTabUtils<string>(SALE_FILTERS_KEY);
|
||||
|
||||
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
||||
createFilterUtils<SaleListUrlQueryParams, SaleListUrlFilters>({
|
||||
|
|
|
@ -6,9 +6,13 @@ import { createGetSortQueryVariables } from "@dashboard/utils/sort";
|
|||
export const DEFAULT_SORT_KEY = SaleListUrlSortField.name;
|
||||
|
||||
export function canBeSorted(
|
||||
sort: SaleListUrlSortField,
|
||||
sort: SaleListUrlSortField | undefined,
|
||||
isChannelSelected: boolean,
|
||||
) {
|
||||
if (sort === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (sort) {
|
||||
case SaleListUrlSortField.name:
|
||||
case SaleListUrlSortField.startDate:
|
||||
|
|
|
@ -314,6 +314,7 @@ export const filterPresetsProps: FilterPresetsProps = {
|
|||
onFilterPresetPresetSave: () => undefined,
|
||||
onFilterPresetUpdate: () => undefined,
|
||||
filterPresets: ["Tab X"],
|
||||
hasPresetsChanged: () => false,
|
||||
};
|
||||
|
||||
export const paginatorContextValues: PaginatorContextValues = {
|
||||
|
|
|
@ -9,10 +9,8 @@ import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
|
|||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export interface OrderDraftListHeaderProps
|
||||
extends Omit<FilterPresetsProps, "onTabDelete"> {
|
||||
export interface OrderDraftListHeaderProps extends FilterPresetsProps {
|
||||
limits: RefreshLimitsQuery["shop"]["limits"];
|
||||
hasPresetsChanged: boolean;
|
||||
isFilterPresetOpen: boolean;
|
||||
disabled: boolean;
|
||||
onAdd: () => void;
|
||||
|
@ -55,7 +53,7 @@ export const OrderDraftListHeader = ({
|
|||
</Box>
|
||||
|
||||
<FilterPresetsSelect
|
||||
presetsChanged={hasPresetsChanged}
|
||||
presetsChanged={hasPresetsChanged()}
|
||||
onSelect={onFilterPresetChange}
|
||||
onRemove={onFilterPresetDelete}
|
||||
onUpdate={onFilterPresetUpdate}
|
||||
|
|
|
@ -69,7 +69,7 @@ const OrderDraftListPage: React.FC<OrderDraftListPageProps> = ({
|
|||
<OrderDraftListHeader
|
||||
disabled={disabled}
|
||||
selectedFilterPreset={selectedFilterPreset}
|
||||
hasPresetsChanged={hasPresetsChanged()}
|
||||
hasPresetsChanged={hasPresetsChanged}
|
||||
isFilterPresetOpen={isFilterPresetOpen}
|
||||
setFilterPresetOpen={setFilterPresetOpen}
|
||||
limits={limits}
|
||||
|
|
|
@ -123,13 +123,14 @@ export interface FilterProps<TKeys extends string> {
|
|||
}
|
||||
|
||||
export interface FilterPresetsProps {
|
||||
selectedFilterPreset: number;
|
||||
selectedFilterPreset: number | undefined;
|
||||
filterPresets: string[];
|
||||
onFilterPresetsAll: () => void;
|
||||
onFilterPresetChange: (id: number) => void;
|
||||
onFilterPresetUpdate: (name: string) => void;
|
||||
onFilterPresetDelete: (id: number) => void;
|
||||
onFilterPresetPresetSave: () => void;
|
||||
hasPresetsChanged: () => boolean;
|
||||
}
|
||||
|
||||
export interface TabPageProps {
|
||||
|
|
Loading…
Reference in a new issue