From 9037c9cfd145557d346f9cc11ee90c771e796ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 3 Aug 2023 10:23:13 +0200 Subject: [PATCH] Introduce datagrid on attributes list view (#4040) --- .changeset/loud-countries-kiss.md | 5 + locale/defaultMessages.json | 25 +- .../AttributeList/AttributeList.tsx | 258 ------------------ .../components/AttributeList/index.ts | 2 - .../AttributeListDatagrid.tsx | 150 ++++++++++ .../AttributeListDatagrid/datagrid.ts | 87 ++++++ .../components/AttributeListDatagrid/index.ts | 1 + .../AttributeListDatagrid/messages.ts | 35 +++ .../AttributeListPage/AttributeListPage.tsx | 137 ++++++---- .../views/AttributeList/AttributeList.tsx | 183 +++++++------ src/attributes/views/AttributeList/filters.ts | 3 +- src/components/AppLayout/TopNav/Root.tsx | 1 + src/config.ts | 5 + 13 files changed, 491 insertions(+), 401 deletions(-) create mode 100644 .changeset/loud-countries-kiss.md delete mode 100644 src/attributes/components/AttributeList/AttributeList.tsx delete mode 100644 src/attributes/components/AttributeList/index.ts create mode 100644 src/attributes/components/AttributeListDatagrid/AttributeListDatagrid.tsx create mode 100644 src/attributes/components/AttributeListDatagrid/datagrid.ts create mode 100644 src/attributes/components/AttributeListDatagrid/index.ts create mode 100644 src/attributes/components/AttributeListDatagrid/messages.ts diff --git a/.changeset/loud-countries-kiss.md b/.changeset/loud-countries-kiss.md new file mode 100644 index 000000000..1a4bd3004 --- /dev/null +++ b/.changeset/loud-countries-kiss.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Introduce datagrid on attributes list view diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 94293f89b..1e32d76be 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -554,9 +554,6 @@ "1X6HtI": { "string": "All Categories" }, - "1div9r": { - "string": "Search Attribute" - }, "1gzck6": { "string": "{firstName} {lastName}" }, @@ -1534,6 +1531,9 @@ "context": "section header", "string": "Media Information" }, + "9ScmSs": { + "string": "Search attributes..." + }, "9Sz0By": { "context": "channel currency", "string": "Currency" @@ -2786,6 +2786,10 @@ "HvJPcU": { "string": "Category deleted" }, + "I+1KzL": { + "context": "tab name", + "string": "All attributes" + }, "I+UwqI": { "context": "is filter range or value", "string": "equal to" @@ -5842,10 +5846,6 @@ "context": "vat included in order price", "string": "VAT included" }, - "dKPMyh": { - "context": "tab name", - "string": "All Attributes" - }, "dM86a2": { "string": "No categories found" }, @@ -6150,6 +6150,9 @@ "g/BrOt": { "string": "Url has invalid format" }, + "g0GAdN": { + "string": "Delete attributes" + }, "g3qjSf": { "context": "button", "string": "Assign categories" @@ -6952,10 +6955,6 @@ "context": "order refund amount, input label", "string": "Amount" }, - "lw9WIk": { - "context": "deleted multiple attributes", - "string": "Attributes successfully delete" - }, "lwjzVj": { "string": "Edit order" }, @@ -8707,6 +8706,10 @@ "context": "hint", "string": "Usually ends with /api/manifest" }, + "z3GGbZ": { + "context": "deleted multiple attributes", + "string": "Attributes successfully deleted" + }, "z8jo8h": { "context": "button", "string": "View products" diff --git a/src/attributes/components/AttributeList/AttributeList.tsx b/src/attributes/components/AttributeList/AttributeList.tsx deleted file mode 100644 index 3edbd5dc0..000000000 --- a/src/attributes/components/AttributeList/AttributeList.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import { - AttributeListUrlSortField, - attributeUrl, -} from "@dashboard/attributes/urls"; -import Checkbox from "@dashboard/components/Checkbox"; -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 { AttributeFragment } from "@dashboard/graphql"; -import { translateBoolean } from "@dashboard/intl"; -import { renderCollection } from "@dashboard/misc"; -import { 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 React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -export interface AttributeListProps - extends ListProps, - ListActions, - SortPage { - attributes: AttributeFragment[]; -} - -const useStyles = makeStyles( - theme => ({ - [theme.breakpoints.up("lg")]: { - colFaceted: { - width: 180, - }, - colName: { - width: "auto", - }, - colSearchable: { - width: 180, - }, - colSlug: { - width: 200, - }, - colVisible: { - width: 180, - }, - }, - colFaceted: { - textAlign: "center", - }, - colName: {}, - colSearchable: { - textAlign: "center", - }, - colSlug: { - paddingLeft: 0, - }, - colVisible: { - textAlign: "center", - }, - link: { - cursor: "pointer", - }, - }), - { name: "AttributeList" }, -); - -const numberOfColumns = 6; - -const AttributeList: React.FC = ({ - attributes, - disabled, - isChecked, - selected, - sort, - toggle, - toggleAll, - toolbar, - onSort, -}) => { - const classes = useStyles({}); - const intl = useIntl(); - - return ( - - - onSort(AttributeListUrlSortField.slug)} - > - - - onSort(AttributeListUrlSortField.name)} - > - - - onSort(AttributeListUrlSortField.visible)} - > - - - onSort(AttributeListUrlSortField.searchable)} - > - - - onSort(AttributeListUrlSortField.useInFacetedSearch)} - > - - - - - - - - - - {renderCollection( - attributes, - attribute => { - const isSelected = attribute ? isChecked(attribute.id) : false; - - return ( - - - toggle(attribute?.id ?? "")} - /> - - - {attribute ? attribute.slug : } - - - {attribute ? attribute.name : } - - - {attribute ? ( - translateBoolean(attribute.visibleInStorefront, intl) - ) : ( - - )} - - - {attribute ? ( - translateBoolean(attribute.filterableInDashboard, intl) - ) : ( - - )} - - - {attribute ? ( - translateBoolean(attribute.filterableInStorefront, intl) - ) : ( - - )} - - - ); - }, - () => ( - - - - - - ), - )} - - - ); -}; -AttributeList.displayName = "AttributeList"; -export default AttributeList; diff --git a/src/attributes/components/AttributeList/index.ts b/src/attributes/components/AttributeList/index.ts deleted file mode 100644 index 9b2e63fa5..000000000 --- a/src/attributes/components/AttributeList/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./AttributeList"; -export * from "./AttributeList"; diff --git a/src/attributes/components/AttributeListDatagrid/AttributeListDatagrid.tsx b/src/attributes/components/AttributeListDatagrid/AttributeListDatagrid.tsx new file mode 100644 index 000000000..a9f5a5bff --- /dev/null +++ b/src/attributes/components/AttributeListDatagrid/AttributeListDatagrid.tsx @@ -0,0 +1,150 @@ +import { + AttributeListUrlSortField, + attributeUrl, +} from "@dashboard/attributes/urls"; +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 { AttributeFragment } from "@dashboard/graphql"; +import useNavigator from "@dashboard/hooks/useNavigator"; +import { 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 { + attributesListStaticColumnsAdapter, + createGetCellContent, +} from "./datagrid"; +import { messages } from "./messages"; + +interface AttributeListDatagridProps + extends ListProps, + SortPage { + attributes: AttributeFragment[]; + onSelectAttributesIds: ( + rowsIndex: number[], + clearSelection: () => void, + ) => void; +} + +export const AttributeListDatagrid = ({ + attributes, + settings, + sort, + disabled, + onSort, + onSelectAttributesIds, + onUpdateListSettings, +}: AttributeListDatagridProps) => { + const datagridState = useDatagridChangeState(); + const navigate = useNavigator(); + const intl = useIntl(); + + const attributesListStaticColumns = useMemo( + () => attributesListStaticColumnsAdapter(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: attributesListStaticColumns, + onSave: onColumnChange, + }); + + const getCellContent = useCallback( + createGetCellContent({ + attributes, + columns: visibleColumns, + intl, + }), + [attributes, intl, visibleColumns], + ); + + const handleRowClick = useCallback( + ([_, row]: Item) => { + const rowData: AttributeFragment = attributes[row]; + + if (rowData) { + navigate(attributeUrl(rowData.id)); + } + }, + [attributes], + ); + + const handleRowAnchor = useCallback( + ([, row]: Item) => attributeUrl(attributes[row].id), + [attributes], + ); + const handleHeaderClick = useCallback( + (col: number) => { + const columnName = visibleColumns[col].id as AttributeListUrlSortField; + onSort(columnName); + }, + [visibleColumns, onSort], + ); + + return ( + + col > 0} + rows={attributes?.length ?? 0} + availableColumns={visibleColumns} + emptyText={intl.formatMessage(messages.empty)} + onRowSelectionChange={onSelectAttributesIds} + getCellContent={getCellContent} + getCellError={() => false} + selectionActions={() => null} + menuItems={() => []} + onRowClick={handleRowClick} + onHeaderClicked={handleHeaderClick} + rowAnchor={handleRowAnchor} + recentlyAddedColumn={recentlyAddedColumn} + renderColumnPicker={() => ( + + )} + /> + + + + + + ); +}; diff --git a/src/attributes/components/AttributeListDatagrid/datagrid.ts b/src/attributes/components/AttributeListDatagrid/datagrid.ts new file mode 100644 index 000000000..6ab7b70fd --- /dev/null +++ b/src/attributes/components/AttributeListDatagrid/datagrid.ts @@ -0,0 +1,87 @@ +import { AttributeListUrlSortField } from "@dashboard/attributes/urls"; +import { PLACEHOLDER } from "@dashboard/components/Datagrid/const"; +import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells"; +import { AvailableColumn } from "@dashboard/components/Datagrid/types"; +import { AttributeFragment } from "@dashboard/graphql"; +import { translateBoolean } from "@dashboard/intl"; +import { Sort } from "@dashboard/types"; +import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon"; +import { GridCell, Item } from "@glideapps/glide-data-grid"; +import { IntlShape } from "react-intl"; + +import { columnsMessages } from "./messages"; + +export const attributesListStaticColumnsAdapter = ( + intl: IntlShape, + sort: Sort, +) => + [ + { + id: "slug", + title: intl.formatMessage(columnsMessages.slug), + width: 300, + }, + { + id: "name", + title: intl.formatMessage(columnsMessages.name), + width: 300, + }, + { + id: "visible", + title: intl.formatMessage(columnsMessages.visible), + width: 200, + }, + { + id: "searchable", + title: intl.formatMessage(columnsMessages.searchable), + width: 200, + }, + { + id: "use-in-faceted-search", + title: intl.formatMessage(columnsMessages.useInFacetedSearch), + width: 200, + }, + ].map(column => ({ + ...column, + icon: getColumnSortDirectionIcon(sort, column.id), + })); + +export const createGetCellContent = + ({ + attributes, + columns, + intl, + }: { + attributes: AttributeFragment[]; + columns: AvailableColumn[]; + intl: IntlShape; + }) => + ([column, row]: Item): GridCell => { + const rowData: AttributeFragment | undefined = attributes[row]; + const columnId = columns[column]?.id; + + if (!columnId || !rowData) { + return readonlyTextCell(""); + } + + switch (columnId) { + case "slug": + return readonlyTextCell(rowData?.slug ?? PLACEHOLDER); + case "name": + return readonlyTextCell(rowData?.name ?? PLACEHOLDER); + case "visible": + return readonlyTextCell( + translateBoolean(rowData?.visibleInStorefront, intl), + ); + case "searchable": + return readonlyTextCell( + translateBoolean(rowData?.filterableInDashboard, intl), + ); + case "use-in-faceted-search": + return readonlyTextCell( + translateBoolean(rowData?.filterableInStorefront, intl), + ); + default: + return readonlyTextCell(""); + } + }; diff --git a/src/attributes/components/AttributeListDatagrid/index.ts b/src/attributes/components/AttributeListDatagrid/index.ts new file mode 100644 index 000000000..03f298460 --- /dev/null +++ b/src/attributes/components/AttributeListDatagrid/index.ts @@ -0,0 +1 @@ +export * from "./AttributeListDatagrid"; diff --git a/src/attributes/components/AttributeListDatagrid/messages.ts b/src/attributes/components/AttributeListDatagrid/messages.ts new file mode 100644 index 000000000..a57e274a6 --- /dev/null +++ b/src/attributes/components/AttributeListDatagrid/messages.ts @@ -0,0 +1,35 @@ +import { defineMessages } from "react-intl"; + +export const columnsMessages = defineMessages({ + slug: { + id: "oJkeS6", + defaultMessage: "Attribute Code", + }, + name: { + id: "HjUoHK", + defaultMessage: "Default Label", + description: "attribute's label'", + }, + visible: { + id: "k6WDZl", + defaultMessage: "Visible", + description: "attribute is visible", + }, + searchable: { + id: "yKuba7", + defaultMessage: "Searchable", + description: "attribute can be searched in dashboard", + }, + useInFacetedSearch: { + defaultMessage: "Use as filter", + id: "Y3pCRX", + description: "attribute can be searched in storefront", + }, +}); + +export const messages = defineMessages({ + empty: { + id: "ztQgD8", + defaultMessage: "No attributes found", + }, +}); diff --git a/src/attributes/components/AttributeListPage/AttributeListPage.tsx b/src/attributes/components/AttributeListPage/AttributeListPage.tsx index f00e2b1fa..6e8198733 100644 --- a/src/attributes/components/AttributeListPage/AttributeListPage.tsx +++ b/src/attributes/components/AttributeListPage/AttributeListPage.tsx @@ -1,26 +1,26 @@ -// @ts-strict-ignore import { attributeAddUrl, AttributeListUrlSortField, } from "@dashboard/attributes/urls"; +import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { TopNav } from "@dashboard/components/AppLayout/TopNav"; -import { Button } from "@dashboard/components/Button"; -import FilterBar from "@dashboard/components/FilterBar"; +import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton"; +import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect"; import { configurationMenuUrl } from "@dashboard/configuration"; import { AttributeFragment } from "@dashboard/graphql"; +import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; 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 { - FilterPageProps, - ListActions, + FilterPagePropsWithPresets, PageListProps, SortPage, - TabPageProps, } from "../../../types"; -import AttributeList from "../AttributeList/AttributeList"; +import { AttributeListDatagrid } from "../AttributeListDatagrid"; import { AttributeFilterKeys, AttributeListFilterOpts, @@ -29,11 +29,12 @@ import { export interface AttributeListPageProps extends PageListProps, - ListActions, - FilterPageProps, - SortPage, - TabPageProps { + FilterPagePropsWithPresets, + SortPage { attributes: AttributeFragment[]; + selectedAttributesIds: string[]; + onAttributesDelete: () => void; + onSelectAttributesIds: (rows: number[], clearSelection: () => void) => void; } const AttributeListPage: React.FC = ({ @@ -41,59 +42,103 @@ const AttributeListPage: React.FC = ({ initialSearch, onFilterChange, onSearchChange, - currentTab, - onAll, - onTabChange, - onTabDelete, - onTabSave, - tabs, + hasPresetsChanged, + onFilterPresetChange, + onFilterPresetDelete, + onFilterPresetPresetSave, + onFilterPresetUpdate, + onFilterPresetsAll, + filterPresets, + selectedFilterPreset, + onAttributesDelete, + selectedAttributesIds, + currencySymbol, ...listProps }) => { const intl = useIntl(); + const navigate = useNavigator(); const structure = createFilterStructure(intl, filterOpts); + const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); return ( <> - + + + + + + + + + + + - + currencySymbol={currencySymbol} initialSearch={initialSearch} - searchPlaceholder={intl.formatMessage({ - id: "1div9r", - defaultMessage: "Search Attribute", - })} - tabs={tabs} - onAll={onAll} onFilterChange={onFilterChange} onSearchChange={onSearchChange} - onTabChange={onTabChange} - onTabDelete={onTabDelete} - onTabSave={onTabSave} + filterStructure={structure} + searchPlaceholder={intl.formatMessage({ + id: "9ScmSs", + defaultMessage: "Search attributes...", + })} + actions={ + + {selectedAttributesIds.length > 0 && ( + + + + )} + + } /> - + + ); diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index 472b36599..e350c390b 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -1,39 +1,34 @@ -// @ts-strict-ignore import { - deleteFilterTab, - getActiveFilters, getFilterOpts, - getFiltersCurrentTab, - getFilterTabs, getFilterVariables, - saveFilterTab, + storageUtils, } from "@dashboard/attributes/views/AttributeList/filters"; import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; -import SaveFilterTabDialog, { - SaveFilterTabDialogFormData, -} from "@dashboard/components/SaveFilterTabDialog"; +import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { useAttributeBulkDeleteMutation, useAttributeListQuery, } from "@dashboard/graphql"; +import { useFilterPresets } from "@dashboard/hooks/useFilterPresets"; +import useListSettings from "@dashboard/hooks/useListSettings"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; +import { usePaginationReset } from "@dashboard/hooks/usePaginationReset"; import usePaginator, { createPaginationState, PaginatorContext, } from "@dashboard/hooks/usePaginator"; +import { useRowSelection } from "@dashboard/hooks/useRowSelection"; +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 } from "@dashboard/utils/maps"; import { getSortParams } from "@dashboard/utils/sort"; -import { DeleteIcon, IconButton } from "@saleor/macaw-ui"; -import React from "react"; +import isEqual from "lodash/isEqual"; +import React, { useCallback } from "react"; import { useIntl } from "react-intl"; -import { PAGINATE_BY } from "../../../config"; -import useBulkActions from "../../../hooks/useBulkActions"; -import { maybe } from "../../../misc"; import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog"; import AttributeListPage from "../../components/AttributeListPage"; import { @@ -51,24 +46,52 @@ interface AttributeListProps { const AttributeList: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); - const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( - params.ids, - ); + const intl = useIntl(); - const paginationState = createPaginationState(PAGINATE_BY, params); + const { updateListSettings, settings } = useListSettings( + ListViews.ATTRIBUTE_LIST, + ); + + usePaginationReset(attributeListUrl, params, settings.rowNumber); + + const paginationState = createPaginationState(settings.rowNumber, params); const queryVariables = React.useMemo( () => ({ ...paginationState, filter: getFilterVariables(params), sort: getSortQueryVariables(params), }), - [params], + [params, settings.rowNumber], ); const { data, loading, refetch } = useAttributeListQuery({ variables: queryVariables, }); + const { + clearRowSelection, + selectedRowIds, + setSelectedRowIds, + setClearDatagridRowSelectionCallback, + } = useRowSelection(params); + + const { + hasPresetsChanged, + onPresetChange, + onPresetDelete, + onPresetSave, + onPresetUpdate, + selectedPreset, + presets, + getPresetNameToDelete, + setPresetIdToDelete, + } = useFilterPresets({ + getUrl: attributeListUrl, + params, + storageUtils, + reset: clearRowSelection, + }); + const [attributeBulkDelete, attributeBulkDeleteOpts] = useAttributeBulkDeleteMutation({ onCompleted: data => { @@ -77,21 +100,17 @@ const AttributeList: React.FC = ({ params }) => { notify({ status: "success", text: intl.formatMessage({ - id: "lw9WIk", - defaultMessage: "Attributes successfully delete", + id: "z3GGbZ", + defaultMessage: "Attributes successfully deleted", description: "deleted multiple attributes", }), }); - reset(); + clearRowSelection(); refetch(); } }, }); - const tabs = getFilterTabs(); - - const currentTab = getFiltersCurrentTab(params, tabs); - const [openModal, closeModal] = createDialogActionHandlers< AttributeListUrlDialog, AttributeListUrlQueryParams @@ -99,34 +118,14 @@ const AttributeList: React.FC = ({ params }) => { const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ - cleanupFn: reset, + cleanupFn: clearRowSelection, createUrl: attributeListUrl, getFilterQueryParam, navigate, params, + keepActiveTab: true, }); - const handleTabChange = (tab: number) => { - reset(); - navigate( - attributeListUrl({ - activeTab: tab.toString(), - ...getFilterTabs()[tab - 1].data, - }), - ); - }; - - const handleTabDelete = () => { - deleteFilterTab(currentTab); - reset(); - navigate(attributeListUrl()); - }; - - const handleTabSave = (data: SaveFilterTabDialogFormData) => { - saveFilterTab(data.name, getActiveFilters(params)); - handleTabChange(tabs.length + 1); - }; - const paginationValues = usePaginator({ pageInfo: data?.attributes?.pageInfo, paginationState, @@ -135,64 +134,84 @@ const AttributeList: React.FC = ({ params }) => { const handleSort = createSortHandler(navigate, attributeListUrl, params); + const attributes = mapEdgesToItems(data?.attributes); + + const handleSelectAttributesIds = useCallback( + (rows: number[], clearSelection: () => void) => { + if (!attributes) { + return; + } + + const rowsIds = rows.map(row => attributes[row]?.id); + const haveSaveValues = isEqual(rowsIds, selectedRowIds); + + if (!haveSaveValues) { + setSelectedRowIds(rowsIds); + } + + setClearDatagridRowSelectionCallback(clearSelection); + }, + [ + attributes, + selectedRowIds, + setClearDatagridRowSelectionCallback, + setSelectedRowIds, + ], + ); + return ( { + setPresetIdToDelete(id); + openModal("delete-search"); + }} + onFilterPresetPresetSave={() => openModal("save-search")} + onFilterPresetChange={onPresetChange} + onFilterPresetUpdate={onPresetUpdate} + hasPresetsChanged={hasPresetsChanged} + onAttributesDelete={() => openModal("remove")} + selectedFilterPreset={selectedPreset} + selectedAttributesIds={selectedRowIds} + filterPresets={presets.map(tab => tab.name)} + attributes={attributes ?? []} disabled={loading || attributeBulkDeleteOpts.loading} filterOpts={getFilterOpts(params)} initialSearch={params.query || ""} - isChecked={isSelected} - onAll={resetFilters} onFilterChange={changeFilters} onSearchChange={handleSearchChange} onSort={handleSort} - onTabChange={handleTabChange} - onTabDelete={() => openModal("delete-search")} - onTabSave={() => openModal("save-search")} - selected={listElements.length} sort={getSortParams(params)} - tabs={tabs.map(tab => tab.name)} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - openModal("remove", { - ids: listElements, - }) - } - > - - - } + onSelectAttributesIds={handleSelectAttributesIds} /> + 0 - } - onConfirm={() => - attributeBulkDelete({ variables: { ids: params?.ids ?? [] } }) - } + open={params.action === "remove" && selectedRowIds.length > 0} + onConfirm={async () => { + await attributeBulkDelete({ variables: { ids: selectedRowIds } }); + clearRowSelection(); + }} onClose={closeModal} - quantity={params.ids?.length ?? 0} + quantity={selectedRowIds.length} /> + + tabs[currentTab - 1].name, "...")} + onSubmit={onPresetDelete} + tabName={getPresetNameToDelete()} /> ); diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts index 2c4f726a8..0a70d2d39 100644 --- a/src/attributes/views/AttributeList/filters.ts +++ b/src/attributes/views/AttributeList/filters.ts @@ -98,8 +98,7 @@ export function getFilterQueryParam( } } -export const { deleteFilterTab, getFilterTabs, saveFilterTab } = - createFilterTabUtils(ATTRIBUTE_FILTERS_KEY); +export const storageUtils = createFilterTabUtils(ATTRIBUTE_FILTERS_KEY); export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils( diff --git a/src/components/AppLayout/TopNav/Root.tsx b/src/components/AppLayout/TopNav/Root.tsx index d29512757..306d90d23 100644 --- a/src/components/AppLayout/TopNav/Root.tsx +++ b/src/components/AppLayout/TopNav/Root.tsx @@ -34,6 +34,7 @@ export const Root: React.FC> = ({ {isPickerActive && ( diff --git a/src/config.ts b/src/config.ts index e01626dc8..89e2d6f72 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,6 +42,7 @@ export type ProductListColumns = export interface AppListViewSettings { [ListViews.APPS_LIST]: ListSettings; [ListViews.ATTRIBUTE_VALUE_LIST]: ListSettings; + [ListViews.ATTRIBUTE_LIST]: ListSettings; [ListViews.CATEGORY_LIST]: ListSettings; [ListViews.COLLECTION_LIST]: ListSettings; [ListViews.CUSTOMER_LIST]: ListSettings; @@ -71,6 +72,10 @@ export const defaultListSettings: AppListViewSettings = { [ListViews.ATTRIBUTE_VALUE_LIST]: { rowNumber: 10, }, + [ListViews.ATTRIBUTE_LIST]: { + rowNumber: 10, + columns: ["slug", "name", "visible", "searchable", "use-in-faceted-search"], + }, [ListViews.CATEGORY_LIST]: { rowNumber: PAGINATE_BY, columns: ["name", "products", "subcategories"],