Introduce datagrid on voucher list view (#4036)

This commit is contained in:
Paweł Chyła 2023-08-02 09:25:07 +02:00 committed by GitHub
parent dac77169ae
commit 41fde64fc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 588 additions and 147 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": minor
---
Introduce datagrid on voucher list view

View file

@ -997,9 +997,6 @@
"50lR2F": { "50lR2F": {
"string": "- used by Payment Plugins" "string": "- used by Payment Plugins"
}, },
"51HE+Q": {
"string": "No sales found"
},
"54KYgS": { "54KYgS": {
"context": "months after label", "context": "months after label",
"string": "months after issue" "string": "months after issue"
@ -2921,9 +2918,6 @@
"context": "window title", "context": "window title",
"string": "Create category" "string": "Create category"
}, },
"IruP2T": {
"string": "Search Voucher"
},
"IwEQvz": { "IwEQvz": {
"context": "success activate alert message", "context": "success activate alert message",
"string": "Successfully activated gift {count,plural,one{card} other{cards}}" "string": "Successfully activated gift {count,plural,one{card} other{cards}}"
@ -5551,6 +5545,9 @@
"context": "product type", "context": "product type",
"string": "Product Type" "string": "Product Type"
}, },
"bPshhv": {
"string": "Search vouchers..."
},
"bRJD/v": { "bRJD/v": {
"context": "button", "context": "button",
"string": "Create permission group" "string": "Create permission group"
@ -6925,6 +6922,9 @@
"context": "section header", "context": "section header",
"string": "Eligible Variants" "string": "Eligible Variants"
}, },
"lfXze9": {
"string": "Delete vouchers"
},
"li1BBk": { "li1BBk": {
"context": "export items as csv file", "context": "export items as csv file",
"string": "Plain CSV file" "string": "Plain CSV file"
@ -7394,9 +7394,9 @@
"context": "status", "context": "status",
"string": "Deactivated" "string": "Deactivated"
}, },
"pNrF72": { "pOUOnw": {
"context": "tab name", "context": "tab name",
"string": "All Vouchers" "string": "All vouchers"
}, },
"pPef6L": { "pPef6L": {
"context": "order payment", "context": "order payment",

View file

@ -127,6 +127,7 @@ export const defaultListSettings: AppListViewSettings = {
}, },
[ListViews.VOUCHER_LIST]: { [ListViews.VOUCHER_LIST]: {
rowNumber: PAGINATE_BY, rowNumber: PAGINATE_BY,
columns: ["code", "min-spent", "start-date", "end-date", "value", "limit"],
}, },
[ListViews.WAREHOUSE_LIST]: { [ListViews.WAREHOUSE_LIST]: {
rowNumber: PAGINATE_BY, rowNumber: PAGINATE_BY,

View file

@ -25,7 +25,7 @@ export const columnsMessages = defineMessages({
export const messages = defineMessages({ export const messages = defineMessages({
empty: { empty: {
id: "51HE+Q", id: "U2mOqA",
defaultMessage: "No sales found", defaultMessage: "No vouchers found",
}, },
}); });

View file

@ -0,0 +1,176 @@
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 { VoucherListUrlSortField, voucherUrl } from "@dashboard/discounts/urls";
import { canBeSorted } from "@dashboard/discounts/views/VoucherList/sort";
import { VoucherFragment } from "@dashboard/graphql";
import useLocale from "@dashboard/hooks/useLocale";
import useNavigator from "@dashboard/hooks/useNavigator";
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 {
createGetCellContent,
vouchersListStaticColumnsAdapter,
} from "./datagrid";
import { messages } from "./messages";
interface VoucherListDatagridProps
extends ListProps,
SortPage<VoucherListUrlSortField>,
ChannelProps {
vouchers: VoucherFragment[];
onSelectVouchersIds: (
rowsIndex: number[],
clearSelection: () => void,
) => void;
}
export const VoucherListDatagrid = ({
vouchers,
settings,
sort,
selectedChannelId,
disabled,
filterDependency,
onSort,
onSelectVouchersIds,
onUpdateListSettings,
}: VoucherListDatagridProps) => {
const datagridState = useDatagridChangeState();
const navigate = useNavigator();
const { locale } = useLocale();
const intl = useIntl();
const vouchersListStaticColumns = useMemo(
() => vouchersListStaticColumnsAdapter(intl, sort),
[intl, sort],
);
const onColumnChange = useCallback(
(picked: string[]) => {
if (onUpdateListSettings) {
onUpdateListSettings("columns", picked.filter(Boolean));
}
},
[onUpdateListSettings],
);
const {
handlers,
visibleColumns,
recentlyAddedColumn,
staticColumns,
selectedColumns,
} = useColumns({
selectedColumns: settings?.columns ?? [],
staticColumns: vouchersListStaticColumns,
onSave: onColumnChange,
});
const getCellContent = useCallback(
createGetCellContent({
vouchers,
columns: visibleColumns,
locale,
selectedChannelId,
}),
[vouchers, selectedChannelId, locale, visibleColumns],
);
const handleRowClick = useCallback(
([_, row]: Item) => {
const rowData: VoucherFragment = vouchers[row];
if (rowData) {
navigate(voucherUrl(rowData.id));
}
},
[vouchers],
);
const handleRowAnchor = useCallback(
([, row]: Item) => voucherUrl(vouchers[row].id),
[vouchers],
);
const handleGetColumnTooltipContent = useCallback(
(col: number): string => {
const columnName = visibleColumns[col].id as VoucherListUrlSortField;
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 VoucherListUrlSortField;
if (canBeSorted(columnName, !!selectedChannelId)) {
onSort(columnName);
}
},
[visibleColumns, onSort],
);
return (
<DatagridChangeStateContext.Provider value={datagridState}>
<Datagrid
readonly
loading={disabled}
rowMarkers="checkbox"
columnSelect="single"
hasRowHover={true}
onColumnMoved={handlers.onMove}
onColumnResize={handlers.onResize}
verticalBorder={col => col > 0}
rows={vouchers?.length ?? 0}
availableColumns={visibleColumns}
emptyText={intl.formatMessage(messages.empty)}
onRowSelectionChange={onSelectVouchersIds}
getCellContent={getCellContent}
getCellError={() => false}
selectionActions={() => null}
menuItems={() => []}
onRowClick={handleRowClick}
onHeaderClicked={handleHeaderClick}
rowAnchor={handleRowAnchor}
getColumnTooltipContent={handleGetColumnTooltipContent}
recentlyAddedColumn={recentlyAddedColumn}
renderColumnPicker={() => (
<ColumnPicker
staticColumns={staticColumns}
selectedColumns={selectedColumns}
onToggle={handlers.onToggle}
/>
)}
/>
<Box paddingX={6}>
<TablePaginationWithContext
component="div"
settings={settings}
disabled={disabled}
onUpdateListSettings={onUpdateListSettings}
/>
</Box>
</DatagridChangeStateContext.Provider>
);
};

View file

@ -0,0 +1,143 @@
import { PLACEHOLDER } from "@dashboard/components/Datagrid/const";
import {
moneyCell,
readonlyTextCell,
} from "@dashboard/components/Datagrid/customCells/cells";
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
import { Locale } from "@dashboard/components/Locale";
import { VoucherListUrlSortField } from "@dashboard/discounts/urls";
import { VoucherFragment } 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 vouchersListStaticColumnsAdapter = (
intl: IntlShape,
sort: Sort<VoucherListUrlSortField>,
) =>
[
{
id: "code",
title: intl.formatMessage(columnsMessages.code),
width: 350,
},
{
id: "min-spent",
title: intl.formatMessage(columnsMessages.minSpent),
width: 200,
},
{
id: "start-date",
title: intl.formatMessage(columnsMessages.starts),
width: 200,
},
{
id: "end-date",
title: intl.formatMessage(columnsMessages.ends),
width: 200,
},
{
id: "value",
title: intl.formatMessage(columnsMessages.value),
width: 200,
},
{
id: "limit",
title: intl.formatMessage(columnsMessages.uses),
width: 200,
},
].map(column => ({
...column,
icon: getColumnSortDirectionIcon(sort, column.id),
}));
export const createGetCellContent =
({
vouchers,
columns,
locale,
selectedChannelId,
}: {
vouchers: VoucherFragment[];
columns: AvailableColumn[];
locale: Locale;
selectedChannelId?: string;
}) =>
([column, row]: Item): GridCell => {
const rowData: VoucherFragment | undefined = vouchers[row];
const columnId = columns[column]?.id;
if (!columnId || !rowData) {
return readonlyTextCell("");
}
const channel = rowData?.channelListings?.find(
lisiting => lisiting.channel.id === selectedChannelId,
);
const hasChannelsLoaded = rowData?.channelListings?.length;
switch (columnId) {
case "code":
return readonlyTextCell(rowData?.code ?? PLACEHOLDER);
case "min-spent":
return rowData?.code && hasChannelsLoaded
? moneyCell(
channel?.minSpent?.amount ?? "",
channel?.minSpent?.currency ?? "",
{
readonly: true,
},
)
: readonlyTextCell(PLACEHOLDER);
case "start-date":
return readonlyTextCell(
rowData.startDate
? moment(rowData.startDate).locale(locale).format("lll")
: PLACEHOLDER,
);
case "end-date":
return readonlyTextCell(
rowData.endDate
? moment(rowData.endDate).locale(locale).format("lll")
: PLACEHOLDER,
);
case "value":
return getVoucherValueCell(rowData, channel);
case "limit":
return readonlyTextCell(
rowData.usageLimit === null
? PLACEHOLDER
: rowData.usageLimit.toString(),
);
default:
return readonlyTextCell("");
}
};
function getVoucherValueCell(
voucher: VoucherFragment,
channel: NonNullable<VoucherFragment["channelListings"]>[number] | undefined,
) {
const hasChannelsLoaded = voucher?.channelListings?.length;
if (!hasChannelsLoaded) {
return readonlyTextCell(PLACEHOLDER);
}
if (voucher?.discountValueType === "FIXED") {
return moneyCell(channel?.discountValue ?? "", channel?.currency ?? "", {
readonly: true,
});
}
return moneyCell(channel?.discountValue ?? "", "%", {
readonly: true,
});
}

View file

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

View file

@ -0,0 +1,41 @@
import { defineMessages } from "react-intl";
export const columnsMessages = defineMessages({
code: {
id: "JsPIOX",
defaultMessage: "Code",
description: "voucher code",
},
minSpent: {
id: "tuYPlG",
defaultMessage: "Min. Spent",
description: "minimum amount of spent money to activate voucher",
},
starts: {
id: "5u7b3V",
defaultMessage: "Starts",
description: "voucher is active from date",
},
ends: {
id: "b6L9n7",
defaultMessage: "Ends",
description: "voucher is active until date",
},
value: {
id: "JV+EiM",
defaultMessage: "Value",
description: "voucher value",
},
uses: {
id: "yHwvLL",
defaultMessage: "Uses",
description: "voucher uses",
},
});
export const messages = defineMessages({
empty: {
id: "U2mOqA",
defaultMessage: "No vouchers found",
},
});

View file

@ -2,12 +2,11 @@
import { voucherList } from "@dashboard/discounts/fixtures"; import { voucherList } from "@dashboard/discounts/fixtures";
import { VoucherListUrlSortField } from "@dashboard/discounts/urls"; import { VoucherListUrlSortField } from "@dashboard/discounts/urls";
import { import {
filterPageProps, filterPresetsProps,
listActionsProps, listActionsProps,
pageListProps, pageListProps,
searchPageProps, searchPageProps,
sortPageProps, sortPageProps,
tabPageProps,
} from "@dashboard/fixtures"; } from "@dashboard/fixtures";
import { DiscountStatusEnum, VoucherDiscountType } from "@dashboard/graphql"; import { DiscountStatusEnum, VoucherDiscountType } from "@dashboard/graphql";
import { Meta, StoryObj } from "@storybook/react"; import { Meta, StoryObj } from "@storybook/react";
@ -20,8 +19,11 @@ const props: VoucherListPageProps = {
...pageListProps.default, ...pageListProps.default,
...searchPageProps, ...searchPageProps,
...sortPageProps, ...sortPageProps,
...tabPageProps, ...filterPresetsProps,
...filterPageProps, onSelectVouchersIds: () => undefined,
selectedVouchersIds: [],
onVoucherDelete: () => undefined,
onFilterChange: () => undefined,
filterOpts: { filterOpts: {
channel: { channel: {
active: false, active: false,
@ -62,6 +64,10 @@ const props: VoucherListPageProps = {
sort: VoucherListUrlSortField.code, sort: VoucherListUrlSortField.code,
}, },
vouchers: voucherList, vouchers: voucherList,
settings: {
rowNumber: 20,
columns: ["code", "min-spent", "start-date", "end-date", "value", "limit"],
},
}; };
const meta: Meta<typeof VoucherListPage> = { const meta: Meta<typeof VoucherListPage> = {
@ -85,6 +91,7 @@ export const Loading: Story = {
args: { args: {
...props, ...props,
vouchers: undefined, vouchers: undefined,
disabled: true,
}, },
parameters: { parameters: {
chromatic: { diffThreshold: 0.85 }, chromatic: { diffThreshold: 0.85 },

View file

@ -1,28 +1,29 @@
// @ts-strict-ignore // @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 { import {
voucherAddUrl, voucherAddUrl,
VoucherListUrlSortField, VoucherListUrlSortField,
} from "@dashboard/discounts/urls"; } from "@dashboard/discounts/urls";
import { VoucherFragment } from "@dashboard/graphql"; import { VoucherFragment } from "@dashboard/graphql";
import useNavigator from "@dashboard/hooks/useNavigator";
import { sectionNames } from "@dashboard/intl"; import { sectionNames } 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 VoucherList from "../VoucherList"; import { VoucherListDatagrid } from "../VoucherListDatagrid";
import { import {
createFilterStructure, createFilterStructure,
VoucherFilterKeys, VoucherFilterKeys,
@ -31,69 +32,119 @@ import {
export interface VoucherListPageProps export interface VoucherListPageProps
extends PageListProps, extends PageListProps,
ListActions, FilterPagePropsWithPresets<VoucherFilterKeys, VoucherListFilterOpts>,
FilterPageProps<VoucherFilterKeys, VoucherListFilterOpts>,
SortPage<VoucherListUrlSortField>, SortPage<VoucherListUrlSortField>,
TabPageProps,
ChannelProps { ChannelProps {
vouchers: VoucherFragment[]; vouchers: VoucherFragment[];
selectedVouchersIds: string[];
onVoucherDelete: () => void;
onSelectVouchersIds: (rows: number[], clearSelection: () => void) => void;
} }
const VoucherListPage: React.FC<VoucherListPageProps> = ({ const VoucherListPage: React.FC<VoucherListPageProps> = ({
currentTab,
filterOpts, filterOpts,
initialSearch, initialSearch,
onAll,
onFilterChange, onFilterChange,
onSearchChange, onSearchChange,
onTabChange, hasPresetsChanged,
onTabDelete, onFilterPresetChange,
onTabSave, onFilterPresetDelete,
tabs, onFilterPresetPresetSave,
onFilterPresetUpdate,
onFilterPresetsAll,
filterPresets,
selectedFilterPreset,
onVoucherDelete,
selectedVouchersIds,
currencySymbol,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const navigate = 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"));
return ( return (
<ListPageLayout> <ListPageLayout>
<TopNav title={intl.formatMessage(sectionNames.vouchers)}> <TopNav
<Button title={intl.formatMessage(sectionNames.vouchers)}
href={voucherAddUrl()} withoutBorder
variant="primary" isAlignToRight={false}
data-test-id="create-voucher" >
<Box
__flex={1}
display="flex"
justifyContent="space-between"
alignItems="center"
> >
<FormattedMessage <Box display="flex">
id="GbhZJ4" <Box marginX={3} display="flex" alignItems="center">
defaultMessage="Create voucher" <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: "pOUOnw",
defaultMessage: "All vouchers",
description: "tab name",
})}
/>
</Box>
<Box>
<Button
onClick={() => navigate(voucherAddUrl())}
variant="primary"
data-test-id="create-voucher"
>
<FormattedMessage
id="GbhZJ4"
defaultMessage="Create voucher"
description="button"
/>
</Button>
</Box>
</Box>
</TopNav> </TopNav>
<Card> <Card>
<FilterBar <ListFilters<VoucherFilterKeys>
allTabLabel={intl.formatMessage({ currencySymbol={currencySymbol}
id: "pNrF72",
defaultMessage: "All Vouchers",
description: "tab name",
})}
currentTab={currentTab}
filterStructure={structure}
initialSearch={initialSearch} initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
id: "IruP2T",
defaultMessage: "Search Voucher",
})}
tabs={tabs}
onAll={onAll}
onFilterChange={onFilterChange} onFilterChange={onFilterChange}
onSearchChange={onSearchChange} onSearchChange={onSearchChange}
onTabChange={onTabChange} filterStructure={structure}
onTabDelete={onTabDelete} searchPlaceholder={intl.formatMessage({
onTabSave={onTabSave} id: "bPshhv",
defaultMessage: "Search vouchers...",
})}
actions={
<Box display="flex" gap={4}>
{selectedVouchersIds.length > 0 && (
<BulkDeleteButton onClick={onVoucherDelete}>
<FormattedMessage
defaultMessage="Delete vouchers"
id="lfXze9"
/>
</BulkDeleteButton>
)}
</Box>
}
/>
<VoucherListDatagrid
filterDependency={filterDependency}
{...listProps}
/> />
<VoucherList filterDependency={filterDependency} {...listProps} />
</Card> </Card>
</ListPageLayout> </ListPageLayout>
); );

View file

@ -2,15 +2,13 @@
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 {
useVoucherBulkDeleteMutation, useVoucherBulkDeleteMutation,
useVoucherListQuery, useVoucherListQuery,
} 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, sectionNames } from "@dashboard/intl"; import { commonMessages, sectionNames } 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 VoucherListPage from "../../components/VoucherListPage"; import VoucherListPage from "../../components/VoucherListPage";
@ -39,14 +37,10 @@ import {
VoucherListUrlQueryParams, VoucherListUrlQueryParams,
} 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,7 @@ interface VoucherListProps {
export const VoucherList: React.FC<VoucherListProps> = ({ params }) => { export const VoucherList: React.FC<VoucherListProps> = ({ 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.VOUCHER_LIST, ListViews.VOUCHER_LIST,
); );
@ -96,17 +88,38 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
variables: queryVariables, variables: queryVariables,
}); });
const tabs = getFilterTabs(); const {
clearRowSelection,
selectedRowIds,
setSelectedRowIds,
setClearDatagridRowSelectionCallback,
} = useRowSelection(params);
const currentTab = getFiltersCurrentTab(params, tabs); const {
hasPresetsChanged,
onPresetChange,
onPresetDelete,
onPresetSave,
onPresetUpdate,
selectedPreset,
presets,
getPresetNameToDelete,
setPresetIdToDelete,
} = useFilterPresets({
getUrl: voucherListUrl,
params,
storageUtils,
reset: clearRowSelection,
});
const [changeFilters, resetFilters, handleSearchChange] = const [changeFilters, resetFilters, handleSearchChange] =
createFilterHandlers({ createFilterHandlers({
cleanupFn: reset, cleanupFn: clearRowSelection,
createUrl: voucherListUrl, createUrl: voucherListUrl,
getFilterQueryParam, getFilterQueryParam,
navigate, navigate,
params, params,
keepActiveTab: true,
}); });
useEffect(() => { useEffect(() => {
@ -120,29 +133,6 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
} }
}, [params]); }, [params]);
const handleTabChange = (tab: number) => {
reset();
navigate(
voucherListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data,
}),
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(voucherListUrl());
};
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: data?.vouchers?.pageInfo, pageInfo: data?.vouchers?.pageInfo,
paginationState, paginationState,
@ -157,66 +147,85 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
status: "success", status: "success",
text: intl.formatMessage(commonMessages.savedChanges), text: intl.formatMessage(commonMessages.savedChanges),
}); });
reset(); clearRowSelection();
closeModal(); closeModal();
refetch(); refetch();
} }
}, },
}); });
const onVoucherBulkDelete = () => const onVoucherBulkDelete = async () => {
voucherBulkDelete({ await voucherBulkDelete({
variables: { variables: {
ids: params.ids, ids: selectedRowIds,
}, },
}); });
clearRowSelection();
};
const handleSort = createSortHandler(navigate, voucherListUrl, params); const handleSort = createSortHandler(navigate, voucherListUrl, params);
const vouchers = mapEdgesToItems(data?.vouchers) ?? [];
const handleSelectVouchersIds = useCallback(
(rows: number[], clearSelection: () => void) => {
if (!vouchers) {
return;
}
const rowsIds = rows.map(row => vouchers[row]?.id);
const haveSaveValues = isEqual(rowsIds, selectedRowIds);
if (!haveSaveValues) {
setSelectedRowIds(rowsIds);
}
setClearDatagridRowSelectionCallback(clearSelection);
},
[
vouchers,
selectedRowIds,
setClearDatagridRowSelectionCallback,
setSelectedRowIds,
],
);
return ( return (
<PaginatorContext.Provider value={paginationValues}> <PaginatorContext.Provider value={paginationValues}>
<WindowTitle title={intl.formatMessage(sectionNames.vouchers)} /> <WindowTitle title={intl.formatMessage(sectionNames.vouchers)} />
<VoucherListPage <VoucherListPage
currentTab={currentTab} onSelectVouchersIds={handleSelectVouchersIds}
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} onFilterPresetsAll={resetFilters}
onTabChange={handleTabChange} onFilterPresetDelete={(id: number) => {
onTabDelete={() => openModal("delete-search")} setPresetIdToDelete(id);
onTabSave={() => openModal("save-search")} openModal("delete-search");
tabs={tabs.map(tab => tab.name)} }}
onFilterPresetPresetSave={() => openModal("save-search")}
onFilterPresetChange={onPresetChange}
onFilterPresetUpdate={onPresetUpdate}
hasPresetsChanged={hasPresetsChanged}
onVoucherDelete={() => openModal("remove")}
selectedFilterPreset={selectedPreset}
selectedVouchersIds={selectedRowIds}
currencySymbol={selectedChannel?.currencyCode}
filterPresets={presets.map(tab => tab.name)}
settings={settings} settings={settings}
vouchers={mapEdgesToItems(data?.vouchers)} vouchers={vouchers}
disabled={loading} disabled={loading}
onUpdateListSettings={updateListSettings} onUpdateListSettings={updateListSettings}
onSort={handleSort} onSort={handleSort}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)} 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 <ActionDialog
confirmButtonState={voucherBulkDeleteOpts.status} confirmButtonState={voucherBulkDeleteOpts.status}
onClose={closeModal} onClose={closeModal}
onConfirm={onVoucherBulkDelete} onConfirm={onVoucherBulkDelete}
open={params.action === "remove" && canOpenBulkActionDialog} open={params.action === "remove" && selectedRowIds.length > 0}
title={intl.formatMessage({ title={intl.formatMessage({
id: "Q0JJ4F", id: "Q0JJ4F",
defaultMessage: "Delete Vouchers", defaultMessage: "Delete Vouchers",
@ -224,32 +233,30 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
})} })}
variant="delete" variant="delete"
> >
{canOpenBulkActionDialog && ( <DialogContentText>
<DialogContentText> <FormattedMessage
<FormattedMessage id="O9QPe1"
id="O9QPe1" defaultMessage="{counter,plural,one{Are you sure you want to delete this voucher?} other{Are you sure you want to delete {displayQuantity} vouchers?}}"
defaultMessage="{counter,plural,one{Are you sure you want to delete this voucher?} other{Are you sure you want to delete {displayQuantity} vouchers?}}" 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={getPresetNameToDelete()}
/> />
</PaginatorContext.Provider> </PaginatorContext.Provider>
); );

View file

@ -151,8 +151,7 @@ export function getFilterQueryParam(
} }
} }
export const { deleteFilterTab, getFilterTabs, saveFilterTab } = export const storageUtils = createFilterTabUtils<string>(VOUCHER_FILTERS_KEY);
createFilterTabUtils<VoucherListUrlFilters>(VOUCHER_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
createFilterUtils<VoucherListUrlQueryParams, VoucherListUrlFilters>({ createFilterUtils<VoucherListUrlQueryParams, VoucherListUrlFilters>({

View file

@ -18,6 +18,7 @@ export interface UseFilterPresets {
onPresetDelete: () => void; onPresetDelete: () => void;
onPresetSave: (data: SaveFilterTabDialogFormData) => void; onPresetSave: (data: SaveFilterTabDialogFormData) => void;
onPresetUpdate: (tabName: string) => void; onPresetUpdate: (tabName: string) => void;
getPresetNameToDelete: () => string;
hasPresetsChanged: () => boolean; hasPresetsChanged: () => boolean;
} }
@ -122,9 +123,18 @@ export const useFilterPresets = <
); );
}; };
const getPresetNameToDelete = (): string => {
const presetIndex = presetIdToDelete ? presetIdToDelete - 1 : 0;
const preset = presets?.[presetIndex];
const tabName = preset?.name ?? "...";
return tabName;
};
return { return {
presetIdToDelete, presetIdToDelete,
setPresetIdToDelete, setPresetIdToDelete,
getPresetNameToDelete,
presets, presets,
selectedPreset, selectedPreset,
onPresetChange, onPresetChange,