From 0995b02dfb38961460fea1ef3026555a88d57130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dro=C5=84?= Date: Tue, 1 Aug 2023 10:30:05 +0200 Subject: [PATCH] Content list datagrid (#4024) * Build types * (wip) Page list datagrid implementation * Bulk actions * Add changeset --- .changeset/spotty-horses-wink.md | 5 + locale/defaultMessages.json | 56 +++-- src/config.ts | 1 + .../views/CustomerList/CustomerList.tsx | 6 +- src/customers/views/CustomerList/filters.ts | 12 - .../useFilterPresets/useFilterPresets.ts | 11 + src/pages/components/PageList/PageList.tsx | 211 ------------------ src/pages/components/PageList/index.ts | 2 - .../PageListDatagrid/PageListDatagrid.tsx | 156 +++++++++++++ .../components/PageListDatagrid/datagrid.ts | 88 ++++++++ .../components/PageListDatagrid/messages.ts | 36 +++ .../PageListPage/PageListPage.stories.tsx | 31 ++- .../components/PageListPage/PageListPage.tsx | 153 ++++++++++--- .../PageListPage/PageListSearchAndFilters.tsx | 136 ----------- src/pages/components/PageListPage/messages.ts | 15 ++ src/pages/types.ts | 5 + src/pages/views/PageList/PageList.tsx | 210 ++++++++++------- .../PageList}/filters.ts | 3 +- 18 files changed, 638 insertions(+), 499 deletions(-) create mode 100644 .changeset/spotty-horses-wink.md delete mode 100644 src/pages/components/PageList/PageList.tsx delete mode 100644 src/pages/components/PageList/index.ts create mode 100644 src/pages/components/PageListDatagrid/PageListDatagrid.tsx create mode 100644 src/pages/components/PageListDatagrid/datagrid.ts create mode 100644 src/pages/components/PageListDatagrid/messages.ts delete mode 100644 src/pages/components/PageListPage/PageListSearchAndFilters.tsx create mode 100644 src/pages/types.ts rename src/pages/{components/PageListPage => views/PageList}/filters.ts (96%) diff --git a/.changeset/spotty-horses-wink.md b/.changeset/spotty-horses-wink.md new file mode 100644 index 000000000..ff999b177 --- /dev/null +++ b/.changeset/spotty-horses-wink.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Introduce read-only datagrid on content view diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 16e370e31..1b02d7ea1 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -388,6 +388,10 @@ "context": "menu item name", "string": "Name" }, + "0XHXTZ": { + "context": "column header", + "string": "Title" + }, "0a0fLZ": { "context": "countries list menu label when no countries are assigned", "string": "There are no countries assigned" @@ -1028,10 +1032,6 @@ "5BajZK": { "string": "View and update your site settings" }, - "5GSYCR": { - "context": "page status", - "string": "Visibility" - }, "5HwLx9": { "context": "alert", "string": "Warehouse limit reached" @@ -2343,10 +2343,6 @@ "F7DxHw": { "string": "Subcategories" }, - "F8gsds": { - "context": "unpublish page, button", - "string": "Unpublish" - }, "FA+MRz": { "string": "Set plugin as active" }, @@ -2804,10 +2800,6 @@ "context": "order refund amount", "string": "Max Refund" }, - "I8dAAe": { - "context": "page internal name", - "string": "Slug" - }, "I8mqqj": { "context": "PageTypeDeleteWarningDialog single assigned items button label", "string": "View pages" @@ -4505,10 +4497,6 @@ "UN+yTt": { "string": "Staff Settings" }, - "UN3qWD": { - "context": "page status", - "string": "Not Published" - }, "UNwG+4": { "context": "dialog content", "string": "{counter,plural,one{Are you sure you want to delete this page?} other{Are you sure you want to delete {displayQuantity} pages?}}" @@ -4635,10 +4623,6 @@ "context": "button", "string": "Create Permission Group" }, - "V2+HTM": { - "context": "dialog header", - "string": "Title" - }, "V2BBQu": { "string": "Currency in both channels must be the same" }, @@ -4890,6 +4874,10 @@ "context": "error message", "string": "Login went wrong. Please try again." }, + "WvMDHa": { + "context": "page status", + "string": "Not published" + }, "Ww69SE": { "context": "search input placeholder", "string": "Search tax classes" @@ -6119,6 +6107,10 @@ "context": "button, form submit, grant refund create", "string": "Grant refund" }, + "ftOPoy": { + "context": "bulk actions button label", + "string": "Unpublish" + }, "ftcHpD": { "string": "Customer created" }, @@ -6295,6 +6287,10 @@ "hJZwTS": { "string": "Email address" }, + "hJrzlT": { + "context": "tab name", + "string": "All content" + }, "hLOEeb": { "context": "button", "string": "Set as default billing address" @@ -6513,6 +6509,10 @@ "context": "label", "string": "Search pages" }, + "j/Oo0B": { + "context": "bulk actions button label", + "string": "Publish" + }, "j/vV0n": { "context": "channel name", "string": "Channel Name" @@ -6697,6 +6697,10 @@ "kFsTMN": { "string": "Delete customers" }, + "kIcyUo": { + "context": "column header", + "string": "Slug" + }, "kIvvax": { "string": "Search Products..." }, @@ -8367,6 +8371,10 @@ "wL7VAE": { "string": "Actions" }, + "wLB8B3": { + "context": "column header", + "string": "Visibility" + }, "wNQzS/": { "string": "The primary address of this customer." }, @@ -8573,10 +8581,6 @@ "context": "dialog content", "string": "{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}" }, - "yEmwxD": { - "context": "publish page, button", - "string": "Publish" - }, "yH56V+": { "string": "Ooops!..." }, @@ -8767,6 +8771,10 @@ "context": "sum of captured amount of all transactions", "string": "Total captured" }, + "zfBsje": { + "context": "bulk actions button label", + "string": "Delete content" + }, "zfjAc7": { "context": "grant refund, card header", "string": "Unfulfilled products" diff --git a/src/config.ts b/src/config.ts index 2db4fab9e..f91ff5a95 100644 --- a/src/config.ts +++ b/src/config.ts @@ -96,6 +96,7 @@ export const defaultListSettings: AppListViewSettings = { }, [ListViews.PAGES_LIST]: { rowNumber: PAGINATE_BY, + columns: ["title", "slug", "visible"], }, [ListViews.PLUGINS_LIST]: { rowNumber: PAGINATE_BY, diff --git a/src/customers/views/CustomerList/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx index b91eb4cc6..4cb3e82e8 100644 --- a/src/customers/views/CustomerList/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -6,7 +6,10 @@ import { useBulkRemoveCustomersMutation, useListCustomersQuery, } from "@dashboard/graphql"; -import { useFilterPresets } from "@dashboard/hooks/useFilterPresets"; +import { + getPresetNameToDelete, + useFilterPresets, +} from "@dashboard/hooks/useFilterPresets"; import useListSettings from "@dashboard/hooks/useListSettings"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; @@ -38,7 +41,6 @@ import { getFilterOpts, getFilterQueryParam, getFilterVariables, - getPresetNameToDelete, storageUtils, } from "./filters"; import { getSortQueryVariables } from "./sort"; diff --git a/src/customers/views/CustomerList/filters.ts b/src/customers/views/CustomerList/filters.ts index 719a793bc..b012b6ee9 100644 --- a/src/customers/views/CustomerList/filters.ts +++ b/src/customers/views/CustomerList/filters.ts @@ -8,7 +8,6 @@ import { CustomerFilterInput } from "@dashboard/graphql"; import { createFilterTabUtils, createFilterUtils, - GetFilterTabsOutput, getGteLteVariables, getMinMaxQueryParam, } from "../../../utils/filters"; @@ -95,14 +94,3 @@ export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils( CustomerListUrlFiltersEnum, ); - -export const getPresetNameToDelete = ( - presets: GetFilterTabsOutput, - presetIdToDelete: number | null, -): string => { - const presetIndex = presetIdToDelete ? presetIdToDelete - 1 : 0; - const preset = presets?.[presetIndex]; - const tabName = preset?.name ?? "..."; - - return tabName; -}; diff --git a/src/hooks/useFilterPresets/useFilterPresets.ts b/src/hooks/useFilterPresets/useFilterPresets.ts index 4fca50fcd..f573c4e41 100644 --- a/src/hooks/useFilterPresets/useFilterPresets.ts +++ b/src/hooks/useFilterPresets/useFilterPresets.ts @@ -134,3 +134,14 @@ export const useFilterPresets = < hasPresetsChanged, }; }; + +export const getPresetNameToDelete = ( + presets: GetFilterTabsOutput, + presetIdToDelete: number | null, +): string => { + const presetIndex = presetIdToDelete ? presetIdToDelete - 1 : 0; + const preset = presets?.[presetIndex]; + const tabName = preset?.name ?? "..."; + + return tabName; +}; diff --git a/src/pages/components/PageList/PageList.tsx b/src/pages/components/PageList/PageList.tsx deleted file mode 100644 index 0e3513bd5..000000000 --- a/src/pages/components/PageList/PageList.tsx +++ /dev/null @@ -1,211 +0,0 @@ -// @ts-strict-ignore -import Checkbox from "@dashboard/components/Checkbox"; -import { Pill } from "@dashboard/components/Pill"; -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 { PageFragment } from "@dashboard/graphql"; -import { maybe, renderCollection } from "@dashboard/misc"; -import { PageListUrlSortField, pageUrl } from "@dashboard/pages/urls"; -import { ListActions, ListProps, SortPage } from "@dashboard/types"; -import { getArrowDirection } from "@dashboard/utils/sort"; -import { Card, TableBody, TableCell, TableFooter } from "@material-ui/core"; -import { makeStyles } from "@saleor/macaw-ui"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -export interface PageListProps - extends ListProps, - ListActions, - SortPage { - pages: PageFragment[]; -} - -const useStyles = makeStyles( - theme => ({ - [theme.breakpoints.up("lg")]: { - colSlug: { - width: 250, - }, - colTitle: {}, - colVisibility: { - width: 200, - }, - }, - colSlug: {}, - colTitle: { - paddingLeft: 0, - }, - colVisibility: {}, - link: { - cursor: "pointer", - }, - }), - { name: "PageList" }, -); - -const numberOfColumns = 4; - -const PageList: React.FC = props => { - const { - settings, - pages, - disabled, - onSort, - onUpdateListSettings, - isChecked, - selected, - sort, - toggle, - toggleAll, - toolbar, - } = props; - const classes = useStyles(props); - - const intl = useIntl(); - - return ( - - - - onSort(PageListUrlSortField.title)} - className={classes.colTitle} - > - - - onSort(PageListUrlSortField.slug)} - className={classes.colSlug} - > - - - onSort(PageListUrlSortField.visible)} - className={classes.colVisibility} - > - - - - - - - - - - {renderCollection( - pages, - page => { - const isSelected = page ? isChecked(page.id) : false; - - return ( - - - toggle(page.id)} - /> - - - {maybe(() => page.title, )} - - - {maybe(() => page.slug, )} - - - {maybe( - () => ( - - ), - , - )} - - - ); - }, - () => ( - - - - - - ), - )} - - - - ); -}; -PageList.displayName = "PageList"; -export default PageList; diff --git a/src/pages/components/PageList/index.ts b/src/pages/components/PageList/index.ts deleted file mode 100644 index ceef274c3..000000000 --- a/src/pages/components/PageList/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./PageList"; -export * from "./PageList"; diff --git a/src/pages/components/PageListDatagrid/PageListDatagrid.tsx b/src/pages/components/PageListDatagrid/PageListDatagrid.tsx new file mode 100644 index 000000000..78d293cba --- /dev/null +++ b/src/pages/components/PageListDatagrid/PageListDatagrid.tsx @@ -0,0 +1,156 @@ +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 { Page, Pages } from "@dashboard/pages/types"; +import { PageListUrlSortField } from "@dashboard/pages/urls"; +import { ListProps, SortPage } from "@dashboard/types"; +import { Item } from "@glideapps/glide-data-grid"; +import { Box, useTheme } from "@saleor/macaw-ui/next"; +import React, { useCallback, useMemo } from "react"; +import { useIntl } from "react-intl"; + +import { createGetCellContent, pageListStaticColumnsAdapter } from "./datagrid"; +import { messages } from "./messages"; + +interface PageListDatagridProps + extends ListProps, + SortPage { + pages: Pages | undefined; + loading: boolean; + hasRowHover?: boolean; + onSelectPageIds: (rowsIndex: number[], clearSelection: () => void) => void; + onRowClick: (id: string) => void; + rowAnchor?: (id: string) => string; +} + +export const PageListDatagrid = ({ + pages, + sort, + loading, + settings, + onUpdateListSettings, + hasRowHover, + onRowClick, + rowAnchor, + onSelectPageIds, + onSort, +}: PageListDatagridProps) => { + const intl = useIntl(); + const datagrid = useDatagridChangeState(); + + const pageListStaticColumns = useMemo( + () => pageListStaticColumnsAdapter(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: pageListStaticColumns, + selectedColumns: settings?.columns ?? [], + onSave: onColumnChange, + }); + + const { themeValues } = useTheme(); + const getCellContent = useCallback( + createGetCellContent({ + pages, + columns: visibleColumns, + intl, + themeValues, + }), + [pages, visibleColumns], + ); + + const handleRowClick = useCallback( + ([_, row]: Item) => { + if (!onRowClick || !pages) { + return; + } + const rowData: Page = pages[row]; + onRowClick(rowData.id); + }, + [onRowClick, pages], + ); + + const handleRowAnchor = useCallback( + ([, row]: Item) => { + if (!rowAnchor || !pages) { + return ""; + } + const rowData: Page = pages[row]; + return rowAnchor(rowData.id); + }, + [rowAnchor, pages], + ); + + const handleHeaderClick = useCallback( + (col: number) => { + const columnName = visibleColumns[col].id as PageListUrlSortField; + + onSort(columnName); + }, + [visibleColumns, onSort], + ); + + return ( + + col > 0} + rows={pages?.length ?? 0} + availableColumns={visibleColumns} + emptyText={intl.formatMessage(messages.empty)} + onRowSelectionChange={onSelectPageIds} + getCellContent={getCellContent} + getCellError={() => false} + selectionActions={() => null} + menuItems={() => []} + onRowClick={handleRowClick} + onHeaderClicked={handleHeaderClick} + rowAnchor={handleRowAnchor} + recentlyAddedColumn={recentlyAddedColumn} + renderColumnPicker={() => ( + + )} + /> + + + + + + ); +}; diff --git a/src/pages/components/PageListDatagrid/datagrid.ts b/src/pages/components/PageListDatagrid/datagrid.ts new file mode 100644 index 000000000..02e897406 --- /dev/null +++ b/src/pages/components/PageListDatagrid/datagrid.ts @@ -0,0 +1,88 @@ +import { + readonlyTextCell, + tagsCell, +} from "@dashboard/components/Datagrid/customCells/cells"; +import { AvailableColumn } from "@dashboard/components/Datagrid/types"; +import { getStatusColor } from "@dashboard/misc"; +import { Pages } from "@dashboard/pages/types"; +import { PageListUrlSortField } from "@dashboard/pages/urls"; +import { Sort } from "@dashboard/types"; +import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon"; +import { GridCell, Item } from "@glideapps/glide-data-grid"; +import { ThemeTokensValues } from "@saleor/macaw-ui/next"; +import { IntlShape } from "react-intl"; + +import { columnsMessages, messages } from "./messages"; + +export const pageListStaticColumnsAdapter = ( + intl: IntlShape, + sort: Sort, +): AvailableColumn[] => + [ + { + id: "title", + title: intl.formatMessage(columnsMessages.title), + width: 450, + }, + { + id: "slug", + title: intl.formatMessage(columnsMessages.slug), + width: 300, + }, + { + id: "visible", + title: intl.formatMessage(columnsMessages.visible), + width: 300, + }, + ].map(column => ({ + ...column, + icon: getColumnSortDirectionIcon(sort, column.id), + })); + +export const createGetCellContent = + ({ + pages, + columns, + intl, + themeValues, + }: { + pages: Pages | undefined; + columns: AvailableColumn[]; + intl: IntlShape; + themeValues: ThemeTokensValues; + }) => + ([column, row]: Item): GridCell => { + const rowData = pages?.[row]; + const columnId = columns[column]?.id; + + if (!columnId || !rowData) { + return readonlyTextCell(""); + } + + switch (columnId) { + case "title": + return readonlyTextCell(rowData?.title ?? ""); + case "slug": + return readonlyTextCell(rowData?.slug ?? ""); + case "visible": + const tag = rowData?.isPublished + ? intl.formatMessage(messages.published) + : intl.formatMessage(messages.notPublished); + return tagsCell( + [ + { + tag, + color: + themeValues.colors.background[ + getStatusColor( + rowData?.isPublished ? "success" : "error", + ) as keyof ThemeTokensValues["colors"]["background"] + ], + }, + ], + [tag], + ); + default: + return readonlyTextCell(""); + } + }; diff --git a/src/pages/components/PageListDatagrid/messages.ts b/src/pages/components/PageListDatagrid/messages.ts new file mode 100644 index 000000000..460373eab --- /dev/null +++ b/src/pages/components/PageListDatagrid/messages.ts @@ -0,0 +1,36 @@ +import { defineMessages } from "react-intl"; + +export const messages = defineMessages({ + empty: { + id: "iMJka8", + defaultMessage: "No pages found", + }, + published: { + id: "G1KzEx", + defaultMessage: "Published", + description: "page status", + }, + notPublished: { + id: "WvMDHa", + defaultMessage: "Not published", + description: "page status", + }, +}); + +export const columnsMessages = defineMessages({ + title: { + id: "0XHXTZ", + defaultMessage: "Title", + description: "column header", + }, + slug: { + id: "kIcyUo", + defaultMessage: "Slug", + description: "column header", + }, + visible: { + id: "wLB8B3", + defaultMessage: "Visibility", + description: "column header", + }, +}); diff --git a/src/pages/components/PageListPage/PageListPage.stories.tsx b/src/pages/components/PageListPage/PageListPage.stories.tsx index 0532b2fdb..fda361163 100644 --- a/src/pages/components/PageListPage/PageListPage.stories.tsx +++ b/src/pages/components/PageListPage/PageListPage.stories.tsx @@ -1,7 +1,10 @@ // @ts-strict-ignore import { + filterPageProps, + filterPresetsProps, listActionsProps, pageListProps, + searchPageProps, sortPageProps, } from "@dashboard/fixtures"; import { pageList } from "@dashboard/pages/fixtures"; @@ -12,21 +15,37 @@ import { PaginatorContextDecorator } from "../../../../.storybook/decorators"; import PageListPage, { PageListPageProps } from "./PageListPage"; const props: PageListPageProps = { + ...filterPageProps, ...listActionsProps, ...pageListProps.default, + ...searchPageProps, ...sortPageProps, + ...filterPresetsProps, + settings: { + ...pageListProps.default.settings, + columns: ["title", "slug", "visible"], + }, pages: pageList, sort: { ...sortPageProps.sort, sort: PageListUrlSortField.title, }, - actionDialogOpts: { - open: () => undefined, - close: () => undefined, - }, - params: { - ids: [], + filterOpts: { + pageType: { + active: false, + value: [], + choices: [], + displayValues: [], + }, }, + selectedPageIds: [], + loading: false, + hasPresetsChanged: () => false, + onSelectPageIds: () => undefined, + onPagesDelete: () => undefined, + onPagesPublish: () => undefined, + onPagesUnpublish: () => undefined, + onPageCreate: () => undefined, }; const meta: Meta = { diff --git a/src/pages/components/PageListPage/PageListPage.tsx b/src/pages/components/PageListPage/PageListPage.tsx index 2126513ab..807647a35 100644 --- a/src/pages/components/PageListPage/PageListPage.tsx +++ b/src/pages/components/PageListPage/PageListPage.tsx @@ -1,19 +1,34 @@ +import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { TopNav } from "@dashboard/components/AppLayout/TopNav"; -import { Button } from "@dashboard/components/Button"; -import { PageFragment } from "@dashboard/graphql"; +import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton"; +import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect"; +import { ListPageLayout } from "@dashboard/components/Layouts"; +import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; +import { Pages } from "@dashboard/pages/types"; import { PageListUrlDialog, PageListUrlQueryParams, PageListUrlSortField, + pageUrl, } from "@dashboard/pages/urls"; -import { ListActions, PageListProps, SortPage } from "@dashboard/types"; +import { + FilterPagePropsWithPresets, + PageListProps, + SortPage, +} from "@dashboard/types"; import { Card } from "@material-ui/core"; +import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import PageList from "../PageList"; -import PageListSearchAndFilters from "./PageListSearchAndFilters"; +import { + createFilterStructure, + PageListFilterKeys, + PageListFilterOpts, +} from "../../views/PageList/filters"; +import { PageListDatagrid } from "../PageListDatagrid/PageListDatagrid"; +import { pagesListSearchAndFiltersMessages as messages } from "./messages"; export interface PageListActionDialogOpts { open: (action: PageListUrlDialog, newParams?: PageListUrlQueryParams) => void; @@ -21,41 +36,125 @@ export interface PageListActionDialogOpts { } export interface PageListPageProps extends PageListProps, - ListActions, + FilterPagePropsWithPresets, SortPage { - pages: PageFragment[]; - params: PageListUrlQueryParams; - actionDialogOpts: PageListActionDialogOpts; - onAdd: () => void; + pages: Pages | undefined; + selectedPageIds: string[]; + loading: boolean; + onSelectPageIds: (rows: number[], clearSelection: () => void) => void; + onPagesDelete: () => void; + onPagesPublish: () => void; + onPagesUnpublish: () => void; + onPageCreate: () => void; } const PageListPage: React.FC = ({ - params, - actionDialogOpts, - onAdd, + selectedFilterPreset, + filterOpts, + initialSearch, + onFilterPresetsAll, + onFilterChange, + onFilterPresetDelete, + onFilterPresetUpdate, + onSearchChange, + onFilterPresetChange, + onFilterPresetPresetSave, + filterPresets, + selectedPageIds, + hasPresetsChanged, + onPagesDelete, + onPagesPublish, + onPagesUnpublish, + onPageCreate, ...listProps }) => { const intl = useIntl(); + const navigate = useNavigator(); + + const structure = createFilterStructure(intl, filterOpts); + const [isFilterPresetOpen, setFilterPresetOpen] = React.useState(false); return ( - <> - - + + + + + + + + + + + + + - 0 && ( + + + + + + + + ) + } + /> + navigate(pageUrl(id))} /> - - + ); }; PageListPage.displayName = "PageListPage"; diff --git a/src/pages/components/PageListPage/PageListSearchAndFilters.tsx b/src/pages/components/PageListPage/PageListSearchAndFilters.tsx deleted file mode 100644 index 2fe49a6e8..000000000 --- a/src/pages/components/PageListPage/PageListSearchAndFilters.tsx +++ /dev/null @@ -1,136 +0,0 @@ -// @ts-strict-ignore -import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; -import FilterBar from "@dashboard/components/FilterBar"; -import SaveFilterTabDialog, { - SaveFilterTabDialogFormData, -} from "@dashboard/components/SaveFilterTabDialog"; -import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config"; -import { getSearchFetchMoreProps } from "@dashboard/hooks/makeTopLevelSearch/utils"; -import useBulkActions from "@dashboard/hooks/useBulkActions"; -import useNavigator from "@dashboard/hooks/useNavigator"; -import { pageListUrl, PageListUrlQueryParams } from "@dashboard/pages/urls"; -import usePageTypeSearch from "@dashboard/searches/usePageTypeSearch"; -import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers"; -import { mapEdgesToItems } from "@dashboard/utils/maps"; -import React from "react"; -import { useIntl } from "react-intl"; - -import { - createFilterStructure, - deleteFilterTab, - getActiveFilters, - getFilterOpts, - getFilterQueryParam, - getFiltersCurrentTab, - getFilterTabs, - saveFilterTab, -} from "./filters"; -import { pagesListSearchAndFiltersMessages as messages } from "./messages"; -import { PageListActionDialogOpts } from "./PageListPage"; - -interface PageListSearchAndFiltersProps { - params: PageListUrlQueryParams; - actionDialogOpts: PageListActionDialogOpts; -} - -const PageListSearchAndFilters: React.FC = ({ - params, - actionDialogOpts, -}) => { - const navigate = useNavigator(); - const intl = useIntl(); - - const defaultSearchVariables = { - variables: { - ...DEFAULT_INITIAL_SEARCH_DATA, - first: 5, - }, - }; - - const { reset } = useBulkActions(params.ids); - - const { - loadMore: fetchMorePageTypes, - search: searchPageTypes, - result: searchPageTypesResult, - } = usePageTypeSearch(defaultSearchVariables); - - const filterOpts = getFilterOpts({ - params, - pageTypes: mapEdgesToItems(searchPageTypesResult?.data?.search), - pageTypesProps: { - ...getSearchFetchMoreProps(searchPageTypesResult, fetchMorePageTypes), - onSearchChange: searchPageTypes, - }, - }); - - const [changeFilters, resetFilters, handleSearchChange] = - createFilterHandlers({ - createUrl: pageListUrl, - getFilterQueryParam, - navigate, - params, - cleanupFn: reset, - }); - - const filterStructure = createFilterStructure(intl, filterOpts); - - const { open: openModal, close: closeModal } = actionDialogOpts; - - const handleTabChange = (tab: number) => { - navigate( - pageListUrl({ - activeTab: tab.toString(), - ...getFilterTabs()[tab - 1].data, - }), - ); - }; - - const tabs = getFilterTabs(); - const currentTab = getFiltersCurrentTab(params, tabs); - - const handleTabSave = (data: SaveFilterTabDialogFormData) => { - saveFilterTab(data.name, getActiveFilters(params)); - handleTabChange(tabs.length + 1); - }; - - const handleTabDelete = () => { - deleteFilterTab(currentTab); - reset(); - navigate(pageListUrl()); - }; - - return ( - <> - name)} - currentTab={currentTab} - onTabDelete={handleTabDelete} - onTabChange={handleTabChange} - onTabSave={() => openModal("save-search")} - /> - - - - ); -}; - -export default PageListSearchAndFilters; diff --git a/src/pages/components/PageListPage/messages.ts b/src/pages/components/PageListPage/messages.ts index 949c9616c..0b7ae5948 100644 --- a/src/pages/components/PageListPage/messages.ts +++ b/src/pages/components/PageListPage/messages.ts @@ -6,4 +6,19 @@ export const pagesListSearchAndFiltersMessages = defineMessages({ defaultMessage: "Search Content", description: "search content placeholder", }, + publish: { + id: "j/Oo0B", + defaultMessage: "Publish", + description: "bulk actions button label", + }, + unpublish: { + id: "ftOPoy", + defaultMessage: "Unpublish", + description: "bulk actions button label", + }, + delete: { + id: "zfBsje", + defaultMessage: "Delete content", + description: "bulk actions button label", + }, }); diff --git a/src/pages/types.ts b/src/pages/types.ts new file mode 100644 index 000000000..a0a0673d8 --- /dev/null +++ b/src/pages/types.ts @@ -0,0 +1,5 @@ +import { PageListQuery } from "@dashboard/graphql"; +import { RelayToFlat } from "@dashboard/types"; + +export type Pages = RelayToFlat>; +export type Page = Pages[number]; diff --git a/src/pages/views/PageList/PageList.tsx b/src/pages/views/PageList/PageList.tsx index 0828ae94d..0cbe10633 100644 --- a/src/pages/views/PageList/PageList.tsx +++ b/src/pages/views/PageList/PageList.tsx @@ -1,13 +1,18 @@ // @ts-strict-ignore import ActionDialog from "@dashboard/components/ActionDialog"; -import { Button } from "@dashboard/components/Button"; +import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; +import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config"; import { usePageBulkPublishMutation, usePageBulkRemoveMutation, usePageListQuery, } from "@dashboard/graphql"; -import useBulkActions from "@dashboard/hooks/useBulkActions"; +import { getSearchFetchMoreProps } from "@dashboard/hooks/makeTopLevelSearch/utils"; +import { + getPresetNameToDelete, + useFilterPresets, +} from "@dashboard/hooks/useFilterPresets"; import useListSettings from "@dashboard/hooks/useListSettings"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; @@ -16,17 +21,18 @@ import usePaginator, { createPaginationState, PaginatorContext, } from "@dashboard/hooks/usePaginator"; -import { maybe } from "@dashboard/misc"; +import { useRowSelection } from "@dashboard/hooks/useRowSelection"; import PageTypePickerDialog from "@dashboard/pages/components/PageTypePickerDialog"; import usePageTypeSearch from "@dashboard/searches/usePageTypeSearch"; import { ListViews } from "@dashboard/types"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; +import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers"; import createSortHandler from "@dashboard/utils/handlers/sortHandler"; import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps"; import { getSortParams } from "@dashboard/utils/sort"; import { DialogContentText } from "@material-ui/core"; -import { DeleteIcon, IconButton } from "@saleor/macaw-ui"; -import React from "react"; +import isEqual from "lodash/isEqual"; +import React, { useCallback } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import PageListPage from "../../components/PageListPage/PageListPage"; @@ -36,6 +42,7 @@ import { PageListUrlDialog, PageListUrlQueryParams, } from "../../urls"; +import { getFilterOpts, getFilterQueryParam, storageUtils } from "./filters"; import { getFilterVariables, getSortQueryVariables } from "./sort"; interface PageListProps { @@ -45,16 +52,46 @@ interface PageListProps { export const PageList: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); - const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( - params.ids, - ); + const intl = useIntl(); const { updateListSettings, settings } = useListSettings( ListViews.PAGES_LIST, ); usePaginationReset(pageListUrl, params, settings.rowNumber); - const intl = useIntl(); + const { + clearRowSelection, + selectedRowIds, + setClearDatagridRowSelectionCallback, + setSelectedRowIds, + } = useRowSelection(params); + + const [changeFilters, resetFilters, handleSearchChange] = + createFilterHandlers({ + cleanupFn: clearRowSelection, + createUrl: pageListUrl, + getFilterQueryParam, + navigate, + params, + keepActiveTab: true, + }); + + const { + selectedPreset, + presets, + hasPresetsChanged, + onPresetChange, + onPresetDelete, + onPresetSave, + onPresetUpdate, + setPresetIdToDelete, + presetIdToDelete, + } = useFilterPresets({ + params, + reset: clearRowSelection, + getUrl: pageListUrl, + storageUtils, + }); const paginationState = createPaginationState(settings.rowNumber, params); const queryVariables = React.useMemo( @@ -70,8 +107,10 @@ export const PageList: React.FC = ({ params }) => { variables: queryVariables, }); + const pages = mapEdgesToItems(data?.pages); + const paginationValues = usePaginator({ - pageInfo: maybe(() => data.pages.pageInfo), + pageInfo: data?.pages?.pageInfo, paginationState, queryString: params, }); @@ -83,7 +122,7 @@ export const PageList: React.FC = ({ params }) => { const [bulkPageRemove, bulkPageRemoveOpts] = usePageBulkRemoveMutation({ onCompleted: data => { - if (data.pageBulkDelete.errors.length === 0) { + if (data.pageBulkDelete?.errors.length === 0) { closeModal(); notify({ status: "success", @@ -93,7 +132,7 @@ export const PageList: React.FC = ({ params }) => { description: "notification", }), }); - reset(); + clearRowSelection(); refetch(); } }, @@ -101,7 +140,7 @@ export const PageList: React.FC = ({ params }) => { const [bulkPagePublish, bulkPagePublishOpts] = usePageBulkPublishMutation({ onCompleted: data => { - if (data.pageBulkPublish.errors.length === 0) { + if (data.pageBulkPublish?.errors.length === 0) { closeModal(); notify({ status: "success", @@ -111,7 +150,7 @@ export const PageList: React.FC = ({ params }) => { description: "notification", }), }); - reset(); + clearRowSelection(); refetch(); } }, @@ -133,66 +172,72 @@ export const PageList: React.FC = ({ params }) => { onFetchMore: loadMoreDialogPageTypes, }; + const filterOpts = getFilterOpts({ + params, + pageTypes: mapEdgesToItems(searchDialogPageTypesOpts?.data?.search), + pageTypesProps: { + ...getSearchFetchMoreProps( + searchDialogPageTypesOpts, + loadMoreDialogPageTypes, + ), + onSearchChange: searchDialogPageTypes, + }, + }); + + const handleSetSelectedPageIds = useCallback( + (rows: number[], clearSelection: () => void) => { + if (!pages) { + return; + } + + const rowsIds = rows.map(row => pages[row].id); + const haveSaveValues = isEqual(rowsIds, selectedRowIds); + + if (!haveSaveValues) { + setSelectedRowIds(rowsIds); + } + + setClearDatagridRowSelectionCallback(clearSelection); + }, + [ + pages, + selectedRowIds, + setClearDatagridRowSelectionCallback, + setSelectedRowIds, + ], + ); + return ( openModal("create-page")} + onPageCreate={() => openModal("create-page")} onSort={handleSort} - actionDialogOpts={{ - open: openModal, - close: closeModal, - }} - params={params} - toolbar={ - <> - - - - openModal("remove", { - ids: listElements, - }) - } - > - - - - } - isChecked={isSelected} - selected={listElements.length} sort={getSortParams(params)} - toggle={toggle} - toggleAll={toggleAll} + selectedPageIds={selectedRowIds} + onPagesDelete={() => openModal("remove", { ids: selectedRowIds })} + onPagesPublish={() => openModal("publish", { ids: selectedRowIds })} + onPagesUnpublish={() => openModal("unpublish", { ids: selectedRowIds })} + onSelectPageIds={handleSetSelectedPageIds} + filterOpts={filterOpts} + onFilterChange={changeFilters} + initialSearch={params?.query ?? ""} + onSearchChange={handleSearchChange} + onFilterPresetChange={onPresetChange} + onFilterPresetDelete={(id: number) => { + setPresetIdToDelete(id); + openModal("delete-search"); + }} + onFilterPresetUpdate={onPresetUpdate} + onFilterPresetPresetSave={() => openModal("save-search")} + selectedFilterPreset={selectedPreset} + filterPresets={presets.map(preset => preset.name)} + hasPresetsChanged={hasPresetsChanged} + onFilterPresetsAll={resetFilters} /> = ({ params }) => { onConfirm={() => bulkPagePublish({ variables: { - ids: params.ids, + ids: selectedRowIds, isPublished: true, }, }) @@ -218,10 +263,8 @@ export const PageList: React.FC = ({ params }) => { defaultMessage="{counter,plural,one{Are you sure you want to publish this page?} other{Are you sure you want to publish {displayQuantity} pages?}}" description="dialog content" values={{ - counter: maybe(() => params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ), + counter: selectedRowIds.length, + displayQuantity: {selectedRowIds.length}, }} /> @@ -233,7 +276,7 @@ export const PageList: React.FC = ({ params }) => { onConfirm={() => bulkPagePublish({ variables: { - ids: params.ids, + ids: selectedRowIds, isPublished: false, }, }) @@ -249,8 +292,8 @@ export const PageList: React.FC = ({ params }) => { defaultMessage="{counter,plural,one{Are you sure you want to unpublish this page?} other{Are you sure you want to unpublish {displayQuantity} pages?}}" description="dialog content" values={{ - counter: maybe(() => params.ids.length), - displayQuantity: {maybe(() => params.ids.length)}, + counter: selectedRowIds.length, + displayQuantity: {selectedRowIds.length}, }} /> @@ -261,7 +304,7 @@ export const PageList: React.FC = ({ params }) => { onConfirm={() => bulkPageRemove({ variables: { - ids: params.ids, + ids: selectedRowIds, }, }) } @@ -277,8 +320,8 @@ export const PageList: React.FC = ({ params }) => { defaultMessage="{counter,plural,one{Are you sure you want to delete this page?} other{Are you sure you want to delete {displayQuantity} pages?}}" description="dialog content" values={{ - counter: maybe(() => params.ids.length), - displayQuantity: {maybe(() => params.ids.length)}, + counter: selectedRowIds.length, + displayQuantity: {selectedRowIds.length}, }} /> @@ -299,6 +342,19 @@ export const PageList: React.FC = ({ params }) => { ) } /> + + ); }; diff --git a/src/pages/components/PageListPage/filters.ts b/src/pages/views/PageList/filters.ts similarity index 96% rename from src/pages/components/PageListPage/filters.ts rename to src/pages/views/PageList/filters.ts index 5c00315be..e982e95af 100644 --- a/src/pages/components/PageListPage/filters.ts +++ b/src/pages/views/PageList/filters.ts @@ -113,8 +113,7 @@ export type PageListUrlQueryParams = Pagination & ActiveTab & Search; -export const { deleteFilterTab, getFilterTabs, saveFilterTab } = - createFilterTabUtils(PAGES_FILTERS_KEY); +export const storageUtils = createFilterTabUtils(PAGES_FILTERS_KEY); export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils(