Introduce new column picker in Categories list (#3876)

This commit is contained in:
Paweł Chyła 2023-07-11 13:19:26 +02:00 committed by GitHub
parent 5b4d07d547
commit b86bb025a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 116 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": minor
---
Introduce new column picker in Categories list

View file

@ -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}
/> />
)} )}
/> />

View file

@ -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", {
title: intl.formatMessage(columnsMessages.categoryName), id: "name",
width: 350, title: intl.formatMessage(columnsMessages.categoryName),
icon: sort width: 350,
? getColumnSortDirectionIcon(sort, CategoryListUrlSortField.name) },
: undefined, {
}, id: "subcategories",
{ title: intl.formatMessage(columnsMessages.subcategories),
id: "subcategories", width: 300,
title: intl.formatMessage(columnsMessages.subcategories), },
width: 300, {
icon: sort id: "products",
? getColumnSortDirectionIcon( title: intl.formatMessage(columnsMessages.numberOfProducts),
sort, width: 300,
CategoryListUrlSortField.subcategoryCount, },
) ].map(column => ({
: undefined, ...column,
}, icon: sort ? getColumnSortDirectionIcon(sort, column.id) : undefined,
{ }));
id: "products",
title: intl.formatMessage(columnsMessages.numberOfProducts),
width: 300,
icon: sort
? getColumnSortDirectionIcon(sort, CategoryListUrlSortField.productCount)
: 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);
} }

View file

@ -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,41 +29,47 @@ export const CategorySubcategories = ({
disabled, disabled,
onCategoriesDelete, onCategoriesDelete,
onSelectCategoriesIds, onSelectCategoriesIds,
}: CategorySubcategoriesProps) => ( settings,
<DashboardCard> onUpdateListSettings,
<DashboardCard.Title> }: CategorySubcategoriesProps) => {
<Box display="flex" justifyContent="space-between" alignItems="center"> return (
<FormattedMessage <DashboardCard>
id="NivJal" <DashboardCard.Title>
defaultMessage="All Subcategories" <Box display="flex" justifyContent="space-between" alignItems="center">
description="section header" <FormattedMessage
/> id="NivJal"
<InternalLink to={categoryAddUrl(categoryId)}> defaultMessage="All Subcategories"
<Button variant="secondary" data-test-id="create-subcategory"> description="section header"
<FormattedMessage />
id="UycVMp" <InternalLink to={categoryAddUrl(categoryId)}>
defaultMessage="Create subcategory" <Button variant="secondary" data-test-id="create-subcategory">
description="button" <FormattedMessage
/> id="UycVMp"
</Button> defaultMessage="Create subcategory"
</InternalLink> description="button"
</Box> />
</DashboardCard.Title> </Button>
</InternalLink>
<CategoryListDatagrid
categories={subcategories}
disabled={disabled}
onSelectCategoriesIds={onSelectCategoriesIds}
selectionActionButton={
<Box paddingRight={5}>
<CategoryDeleteButton onClick={onCategoriesDelete}>
<FormattedMessage
defaultMessage="Bulk categories delete"
id="ZN5IZl"
/>
</CategoryDeleteButton>
</Box> </Box>
} </DashboardCard.Title>
/>
</DashboardCard> <CategoryListDatagrid
); settings={settings}
onUpdateListSettings={onUpdateListSettings}
categories={subcategories}
disabled={disabled}
onSelectCategoriesIds={onSelectCategoriesIds}
selectionActionButton={
<Box paddingRight={5}>
<CategoryDeleteButton onClick={onCategoriesDelete}>
<FormattedMessage
defaultMessage="Bulk categories delete"
id="ZN5IZl"
/>
</CategoryDeleteButton>
</Box>
}
/>
</DashboardCard>
);
};

View file

@ -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}

View file

@ -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)}

View file

@ -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,