Introduce new column picker in Categories list (#3876)
This commit is contained in:
parent
5b4d07d547
commit
b86bb025a2
7 changed files with 143 additions and 116 deletions
5
.changeset/fast-rocks-punch.md
Normal file
5
.changeset/fast-rocks-punch.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduce new column picker in Categories list
|
|
@ -1,11 +1,10 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import {
|
import {
|
||||||
CategoryListUrlSortField,
|
CategoryListUrlSortField,
|
||||||
categoryUrl,
|
categoryUrl,
|
||||||
} from "@dashboard/categories/urls";
|
} from "@dashboard/categories/urls";
|
||||||
import ColumnPicker from "@dashboard/components/ColumnPicker";
|
import { ColumnPicker } from "@dashboard/components/Datagrid/ColumnPicker/ColumnPicker";
|
||||||
|
import { useColumns } from "@dashboard/components/Datagrid/ColumnPicker/useColumns";
|
||||||
import Datagrid from "@dashboard/components/Datagrid/Datagrid";
|
import Datagrid from "@dashboard/components/Datagrid/Datagrid";
|
||||||
import { useColumnsDefault } from "@dashboard/components/Datagrid/hooks/useColumnsDefault";
|
|
||||||
import {
|
import {
|
||||||
DatagridChangeStateContext,
|
DatagridChangeStateContext,
|
||||||
useDatagridChangeState,
|
useDatagridChangeState,
|
||||||
|
@ -18,14 +17,16 @@ import { Box } from "@saleor/macaw-ui/next";
|
||||||
import React, { ReactNode, useCallback, useMemo } from "react";
|
import React, { ReactNode, useCallback, useMemo } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { createGetCellContent, getColumns } from "./datagrid";
|
import {
|
||||||
|
categoryListStaticColumnsAdapter,
|
||||||
|
createGetCellContent,
|
||||||
|
} from "./datagrid";
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
|
|
||||||
interface CategoryListDatagridProps
|
interface CategoryListDatagridProps
|
||||||
extends Partial<SortPage<CategoryListUrlSortField>>,
|
extends PageListProps,
|
||||||
PageListProps {
|
Partial<SortPage<CategoryListUrlSortField>> {
|
||||||
categories?: CategoryFragment[];
|
categories: CategoryFragment[];
|
||||||
disabled: boolean;
|
|
||||||
onSelectCategoriesIds: (ids: number[], clearSelection: () => void) => void;
|
onSelectCategoriesIds: (ids: number[], clearSelection: () => void) => void;
|
||||||
selectionActionButton?: ReactNode | null;
|
selectionActionButton?: ReactNode | null;
|
||||||
hasRowHover?: boolean;
|
hasRowHover?: boolean;
|
||||||
|
@ -44,32 +45,40 @@ export const CategoryListDatagrid = ({
|
||||||
}: CategoryListDatagridProps) => {
|
}: CategoryListDatagridProps) => {
|
||||||
const datagridState = useDatagridChangeState();
|
const datagridState = useDatagridChangeState();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const availableColumns = useMemo(() => getColumns(intl, sort), [intl, sort]);
|
|
||||||
|
|
||||||
const {
|
const memoizedStaticColumns = useMemo(
|
||||||
availableColumnsChoices,
|
() => categoryListStaticColumnsAdapter(intl, sort),
|
||||||
columnChoices,
|
[intl, sort],
|
||||||
columns,
|
);
|
||||||
defaultColumns,
|
|
||||||
onColumnMoved,
|
const handleColumnChange = useCallback(
|
||||||
onColumnResize,
|
picked => {
|
||||||
onColumnsChange,
|
if (onUpdateListSettings) {
|
||||||
picker,
|
onUpdateListSettings("columns", picked.filter(Boolean));
|
||||||
} = useColumnsDefault(availableColumns);
|
}
|
||||||
|
},
|
||||||
|
[onUpdateListSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { handlers, selectedColumns, staticColumns, visibleColumns } =
|
||||||
|
useColumns({
|
||||||
|
staticColumns: memoizedStaticColumns,
|
||||||
|
selectedColumns: settings?.columns ?? [],
|
||||||
|
onSave: handleColumnChange,
|
||||||
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const getCellContent = useCallback(
|
const getCellContent = useCallback(
|
||||||
createGetCellContent(categories, columns),
|
createGetCellContent(categories, visibleColumns),
|
||||||
[categories, columns],
|
[categories, visibleColumns],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleHeaderClick = useCallback(
|
const handleHeaderClick = useCallback(
|
||||||
(col: number) => {
|
(col: number) => {
|
||||||
if (sort !== undefined) {
|
if (sort !== undefined && onSort) {
|
||||||
onSort(columns[col].id as CategoryListUrlSortField);
|
onSort(visibleColumns[col].id as CategoryListUrlSortField);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[columns, onSort, sort],
|
[visibleColumns, onSort, sort],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRowAnchor = useCallback(
|
const handleRowAnchor = useCallback(
|
||||||
|
@ -86,7 +95,7 @@ export const CategoryListDatagrid = ({
|
||||||
columnSelect={sort !== undefined ? "single" : undefined}
|
columnSelect={sort !== undefined ? "single" : undefined}
|
||||||
verticalBorder={col => col > 0}
|
verticalBorder={col => col > 0}
|
||||||
rowMarkers="checkbox"
|
rowMarkers="checkbox"
|
||||||
availableColumns={columns}
|
availableColumns={visibleColumns}
|
||||||
rows={categories?.length ?? 0}
|
rows={categories?.length ?? 0}
|
||||||
getCellContent={getCellContent}
|
getCellContent={getCellContent}
|
||||||
getCellError={() => false}
|
getCellError={() => false}
|
||||||
|
@ -96,21 +105,14 @@ export const CategoryListDatagrid = ({
|
||||||
menuItems={() => []}
|
menuItems={() => []}
|
||||||
actionButtonPosition="right"
|
actionButtonPosition="right"
|
||||||
selectionActions={() => selectionActionButton}
|
selectionActions={() => selectionActionButton}
|
||||||
onColumnResize={onColumnResize}
|
onColumnResize={handlers.onResize}
|
||||||
onColumnMoved={onColumnMoved}
|
onColumnMoved={handlers.onMove}
|
||||||
onRowSelectionChange={onSelectCategoriesIds}
|
onRowSelectionChange={onSelectCategoriesIds}
|
||||||
renderColumnPicker={defaultProps => (
|
renderColumnPicker={() => (
|
||||||
<ColumnPicker
|
<ColumnPicker
|
||||||
{...defaultProps}
|
onSave={handlers.onChange}
|
||||||
availableColumns={availableColumnsChoices}
|
selectedColumns={selectedColumns}
|
||||||
initialColumns={columnChoices}
|
staticColumns={staticColumns}
|
||||||
defaultColumns={defaultColumns}
|
|
||||||
onSave={onColumnsChange}
|
|
||||||
hasMore={false}
|
|
||||||
loading={false}
|
|
||||||
onFetchMore={() => undefined}
|
|
||||||
onQueryChange={picker.setQuery}
|
|
||||||
query={picker.query}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { CategoryListUrlSortField } from "@dashboard/categories/urls";
|
import { CategoryListUrlSortField } from "@dashboard/categories/urls";
|
||||||
import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells";
|
import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells";
|
||||||
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
||||||
|
@ -10,57 +9,52 @@ import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
import { columnsMessages } from "./messages";
|
import { columnsMessages } from "./messages";
|
||||||
|
|
||||||
export const getColumns = (
|
export const categoryListStaticColumnsAdapter = (
|
||||||
intl: IntlShape,
|
intl: IntlShape,
|
||||||
sort?: Sort<CategoryListUrlSortField>,
|
sort?: Sort<CategoryListUrlSortField>,
|
||||||
): AvailableColumn[] => [
|
): AvailableColumn[] =>
|
||||||
|
[
|
||||||
{
|
{
|
||||||
id: "name",
|
id: "name",
|
||||||
title: intl.formatMessage(columnsMessages.categoryName),
|
title: intl.formatMessage(columnsMessages.categoryName),
|
||||||
width: 350,
|
width: 350,
|
||||||
icon: sort
|
|
||||||
? getColumnSortDirectionIcon(sort, CategoryListUrlSortField.name)
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "subcategories",
|
id: "subcategories",
|
||||||
title: intl.formatMessage(columnsMessages.subcategories),
|
title: intl.formatMessage(columnsMessages.subcategories),
|
||||||
width: 300,
|
width: 300,
|
||||||
icon: sort
|
|
||||||
? getColumnSortDirectionIcon(
|
|
||||||
sort,
|
|
||||||
CategoryListUrlSortField.subcategoryCount,
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "products",
|
id: "products",
|
||||||
title: intl.formatMessage(columnsMessages.numberOfProducts),
|
title: intl.formatMessage(columnsMessages.numberOfProducts),
|
||||||
width: 300,
|
width: 300,
|
||||||
icon: sort
|
|
||||||
? getColumnSortDirectionIcon(sort, CategoryListUrlSortField.productCount)
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
];
|
].map(column => ({
|
||||||
|
...column,
|
||||||
|
icon: sort ? getColumnSortDirectionIcon(sort, column.id) : undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
export const createGetCellContent =
|
export const createGetCellContent =
|
||||||
(categories: CategoryFragment[], columns: AvailableColumn[]) =>
|
(categories: CategoryFragment[], columns: AvailableColumn[]) =>
|
||||||
([column, row]: Item): GridCell => {
|
([column, row]: Item): GridCell => {
|
||||||
const columnId = columns[column]?.id;
|
const columnId = columns[column]?.id;
|
||||||
|
const rowData: CategoryFragment | undefined = categories[row];
|
||||||
|
|
||||||
if (!columnId) {
|
if (!columnId || !rowData) {
|
||||||
return readonlyTextCell("");
|
return readonlyTextCell("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowData = categories[row];
|
|
||||||
|
|
||||||
switch (columnId) {
|
switch (columnId) {
|
||||||
case "name":
|
case "name":
|
||||||
return readonlyTextCell(rowData?.name ?? "");
|
return readonlyTextCell(rowData?.name ?? "");
|
||||||
case "subcategories":
|
case "subcategories":
|
||||||
return readonlyTextCell(rowData?.children?.totalCount.toString() ?? "");
|
return readonlyTextCell(
|
||||||
|
rowData?.children?.totalCount?.toString() ?? "",
|
||||||
|
);
|
||||||
case "products":
|
case "products":
|
||||||
return readonlyTextCell(rowData?.products?.totalCount.toString() ?? "");
|
return readonlyTextCell(
|
||||||
|
rowData?.products?.totalCount?.toString() ?? "",
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return readonlyTextCell("", false);
|
return readonlyTextCell("", false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { categoryAddUrl } from "@dashboard/categories/urls";
|
||||||
import { DashboardCard } from "@dashboard/components/Card";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import { InternalLink } from "@dashboard/components/InternalLink";
|
import { InternalLink } from "@dashboard/components/InternalLink";
|
||||||
import { CategoryDetailsQuery } from "@dashboard/graphql";
|
import { CategoryDetailsQuery } from "@dashboard/graphql";
|
||||||
import { RelayToFlat } from "@dashboard/types";
|
import { ListProps, ListViews, RelayToFlat } from "@dashboard/types";
|
||||||
import { Box, Button } from "@saleor/macaw-ui/next";
|
import { Box, Button } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
@ -11,7 +11,11 @@ import { FormattedMessage } from "react-intl";
|
||||||
import { CategoryDeleteButton } from "../CategoryDeleteButton";
|
import { CategoryDeleteButton } from "../CategoryDeleteButton";
|
||||||
import { CategoryListDatagrid } from "../CategoryListDatagrid";
|
import { CategoryListDatagrid } from "../CategoryListDatagrid";
|
||||||
|
|
||||||
interface CategorySubcategoriesProps {
|
interface CategorySubcategoriesProps
|
||||||
|
extends Pick<
|
||||||
|
ListProps<ListViews.CATEGORY_LIST>,
|
||||||
|
"onUpdateListSettings" | "settings"
|
||||||
|
> {
|
||||||
categoryId: string;
|
categoryId: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
subcategories: RelayToFlat<CategoryDetailsQuery["category"]["children"]>;
|
subcategories: RelayToFlat<CategoryDetailsQuery["category"]["children"]>;
|
||||||
|
@ -25,7 +29,10 @@ export const CategorySubcategories = ({
|
||||||
disabled,
|
disabled,
|
||||||
onCategoriesDelete,
|
onCategoriesDelete,
|
||||||
onSelectCategoriesIds,
|
onSelectCategoriesIds,
|
||||||
}: CategorySubcategoriesProps) => (
|
settings,
|
||||||
|
onUpdateListSettings,
|
||||||
|
}: CategorySubcategoriesProps) => {
|
||||||
|
return (
|
||||||
<DashboardCard>
|
<DashboardCard>
|
||||||
<DashboardCard.Title>
|
<DashboardCard.Title>
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
|
@ -47,6 +54,8 @@ export const CategorySubcategories = ({
|
||||||
</DashboardCard.Title>
|
</DashboardCard.Title>
|
||||||
|
|
||||||
<CategoryListDatagrid
|
<CategoryListDatagrid
|
||||||
|
settings={settings}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
categories={subcategories}
|
categories={subcategories}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onSelectCategoriesIds={onSelectCategoriesIds}
|
onSelectCategoriesIds={onSelectCategoriesIds}
|
||||||
|
@ -63,3 +72,4 @@ export const CategorySubcategories = ({
|
||||||
/>
|
/>
|
||||||
</DashboardCard>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -16,7 +16,7 @@ import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import { RelayToFlat } from "../../../types";
|
import { ListProps, ListViews, RelayToFlat } from "../../../types";
|
||||||
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
|
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
|
||||||
import CategoryBackground from "../CategoryBackground";
|
import CategoryBackground from "../CategoryBackground";
|
||||||
import { CategoryProducts } from "../CategoryProducts";
|
import { CategoryProducts } from "../CategoryProducts";
|
||||||
|
@ -28,7 +28,11 @@ export enum CategoryPageTab {
|
||||||
products = "products",
|
products = "products",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryUpdatePageProps {
|
export interface CategoryUpdatePageProps
|
||||||
|
extends Pick<
|
||||||
|
ListProps<ListViews.CATEGORY_LIST>,
|
||||||
|
"onUpdateListSettings" | "settings"
|
||||||
|
> {
|
||||||
categoryId: string;
|
categoryId: string;
|
||||||
changeTab: (index: CategoryPageTab) => void;
|
changeTab: (index: CategoryPageTab) => void;
|
||||||
currentTab: CategoryPageTab;
|
currentTab: CategoryPageTab;
|
||||||
|
@ -70,6 +74,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
onCategoriesDelete,
|
onCategoriesDelete,
|
||||||
onProductsDelete,
|
onProductsDelete,
|
||||||
onSelectProductsIds,
|
onSelectProductsIds,
|
||||||
|
settings,
|
||||||
|
onUpdateListSettings,
|
||||||
}: CategoryUpdatePageProps) => {
|
}: CategoryUpdatePageProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
@ -161,6 +167,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
{currentTab === CategoryPageTab.categories && (
|
{currentTab === CategoryPageTab.categories && (
|
||||||
<CategorySubcategories
|
<CategorySubcategories
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
|
settings={settings}
|
||||||
subcategories={subcategories}
|
subcategories={subcategories}
|
||||||
onCategoriesDelete={onCategoriesDelete}
|
onCategoriesDelete={onCategoriesDelete}
|
||||||
onSelectCategoriesIds={onSelectCategoriesIds}
|
onSelectCategoriesIds={onSelectCategoriesIds}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
useUpdateMetadataMutation,
|
useUpdateMetadataMutation,
|
||||||
useUpdatePrivateMetadataMutation,
|
useUpdatePrivateMetadataMutation,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
|
import useListSettings from "@dashboard/hooks/useListSettings";
|
||||||
import useLocalPaginator, {
|
import useLocalPaginator, {
|
||||||
useSectionLocalPaginationState,
|
useSectionLocalPaginationState,
|
||||||
} from "@dashboard/hooks/useLocalPaginator";
|
} from "@dashboard/hooks/useLocalPaginator";
|
||||||
|
@ -23,6 +24,7 @@ import useNotifier from "@dashboard/hooks/useNotifier";
|
||||||
import { PaginatorContext } from "@dashboard/hooks/usePaginator";
|
import { PaginatorContext } from "@dashboard/hooks/usePaginator";
|
||||||
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
|
import { useRowSelection } from "@dashboard/hooks/useRowSelection";
|
||||||
import { commonMessages, errorMessages } from "@dashboard/intl";
|
import { commonMessages, errorMessages } from "@dashboard/intl";
|
||||||
|
import { ListViews } from "@dashboard/types";
|
||||||
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
||||||
import createMetadataUpdateHandler from "@dashboard/utils/handlers/metadataUpdateHandler";
|
import createMetadataUpdateHandler from "@dashboard/utils/handlers/metadataUpdateHandler";
|
||||||
import { mapEdgesToItems } from "@dashboard/utils/maps";
|
import { mapEdgesToItems } from "@dashboard/utils/maps";
|
||||||
|
@ -98,6 +100,9 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { settings, updateListSettings } =
|
||||||
|
useListSettings<ListViews.CATEGORY_LIST>(ListViews.CATEGORY_LIST);
|
||||||
|
|
||||||
const { data, loading, refetch } = useCategoryDetailsQuery({
|
const { data, loading, refetch } = useCategoryDetailsQuery({
|
||||||
displayLoader: true,
|
displayLoader: true,
|
||||||
variables: { ...paginationState, id },
|
variables: { ...paginationState, id },
|
||||||
|
@ -274,6 +279,8 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
<WindowTitle title={maybe(() => data.category.name)} />
|
<WindowTitle title={maybe(() => data.category.name)} />
|
||||||
<CategoryUpdatePage
|
<CategoryUpdatePage
|
||||||
categoryId={id}
|
categoryId={id}
|
||||||
|
settings={settings}
|
||||||
|
onUpdateListSettings={updateListSettings}
|
||||||
changeTab={changeTab}
|
changeTab={changeTab}
|
||||||
currentTab={activeTab}
|
currentTab={activeTab}
|
||||||
category={maybe(() => data.category)}
|
category={maybe(() => data.category)}
|
||||||
|
|
|
@ -72,6 +72,7 @@ export const defaultListSettings: AppListViewSettings = {
|
||||||
},
|
},
|
||||||
[ListViews.CATEGORY_LIST]: {
|
[ListViews.CATEGORY_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
|
columns: ["name", "products", "subcategories"],
|
||||||
},
|
},
|
||||||
[ListViews.COLLECTION_LIST]: {
|
[ListViews.COLLECTION_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
|
|
Loading…
Reference in a new issue