From 7d48cded75bb0bf6cab040a40580251307a7d6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Mon, 28 Aug 2023 15:25:27 +0200 Subject: [PATCH] Introduce datagrid to permission group list (#4102) Co-authored-by: wojteknowacki --- .changeset/sharp-wombats-teach.md | 5 + cypress/e2e/configuration/permissions.js | 51 ++--- locale/defaultMessages.json | 3 + src/config.ts | 1 + .../PermissionGroupDetailsPage.stories.tsx | 1 + .../PermissionGroupDetailsPage.tsx | 3 + .../PermissionGroupList.tsx | 176 ------------------ .../components/PermissionGroupList/index.ts | 2 - .../PermissionGroupListDatagrid.tsx | 136 ++++++++++++++ .../PermissionGroupListDatagrid/datagrid.ts | 61 ++++++ .../PermissionGroupListDatagrid/index.ts | 1 + .../PermissionGroupListDatagrid/messages.ts | 24 +++ .../PermissionGroupListPage.stories.tsx | 1 - .../PermissionGroupListPage.tsx | 12 +- .../PermissionGroupDetails.tsx | 46 ++++- .../PermissionGroupList.tsx | 61 +----- .../views/PermissionGroupList/sort.ts | 9 + 17 files changed, 319 insertions(+), 274 deletions(-) create mode 100644 .changeset/sharp-wombats-teach.md delete mode 100644 src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx delete mode 100644 src/permissionGroups/components/PermissionGroupList/index.ts create mode 100644 src/permissionGroups/components/PermissionGroupListDatagrid/PermissionGroupListDatagrid.tsx create mode 100644 src/permissionGroups/components/PermissionGroupListDatagrid/datagrid.ts create mode 100644 src/permissionGroups/components/PermissionGroupListDatagrid/index.ts create mode 100644 src/permissionGroups/components/PermissionGroupListDatagrid/messages.ts diff --git a/.changeset/sharp-wombats-teach.md b/.changeset/sharp-wombats-teach.md new file mode 100644 index 000000000..4e9e6efea --- /dev/null +++ b/.changeset/sharp-wombats-teach.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Add datagrid feature to permission group list diff --git a/cypress/e2e/configuration/permissions.js b/cypress/e2e/configuration/permissions.js index bfbdfca44..587f75cd0 100644 --- a/cypress/e2e/configuration/permissions.js +++ b/cypress/e2e/configuration/permissions.js @@ -3,12 +3,8 @@ import faker from "faker"; -import { - PERMISSION_GROUP_DETAILS_SELECTORS, -} from "../../elements/permissionGroup/permissionGroupDetails"; -import { - PERMISSION_GROUP_LIST_SELECTORS, -} from "../../elements/permissionGroup/permissionGroupsList"; +import { PERMISSION_GROUP_DETAILS_SELECTORS } from "../../elements/permissionGroup/permissionGroupDetails"; +import { PERMISSION_GROUP_LIST_SELECTORS } from "../../elements/permissionGroup/permissionGroupsList"; import { BUTTON_SELECTORS } from "../../elements/shared/button-selectors"; import { SHARED_ELEMENTS } from "../../elements/shared/sharedElements"; import { @@ -21,9 +17,8 @@ import { createPermissionGroup, getPermissionGroup, } from "../../support/api/requests/PermissionGroup.js"; -import { - getStaffMembersStartsWith, -} from "../../support/api/requests/StaffMembers"; +import { getStaffMembersStartsWith } from "../../support/api/requests/StaffMembers"; +import { ensureCanvasStatic } from "../../support/customCommands/sharedElementsOperations/canvas"; describe("Permissions groups", () => { const startsWith = "CyPermissions-" + Date.now(); @@ -69,37 +64,31 @@ describe("Permissions groups", () => { () => { const permissionName = `A-${startsWith}${faker.datatype.number()}`; let staffMember; - + cy.addAliasToGraphRequest("PermissionGroupDelete"); getStaffMembersStartsWith(TEST_ADMIN_USER.email) .its("body.data.staffUsers.edges") .then(staffMemberResp => { staffMember = staffMemberResp[0].node; - createPermissionGroup({ name: permissionName, userIdsArray: `["${staffMember.id}"]`, permissionsArray: permissionManageProducts, + }).then(createPermissionGroupResponse => { + cy.visit( + urlList.permissionsGroups + + createPermissionGroupResponse.group.id, + ); + cy.contains(SHARED_ELEMENTS.header, permissionName); + cy.get(BUTTON_SELECTORS.deleteButton).click(); + cy.clickSubmitButton().waitForRequestAndCheckIfNoErrors( + "@PermissionGroupDelete", + ); + ensureCanvasStatic(SHARED_ELEMENTS.dataGridTable); + cy.get(SHARED_ELEMENTS.dataGridTable).should( + "not.contain.text", + permissionName, + ); }); - cy.visit(urlList.permissionsGroups); - cy.contains( - PERMISSION_GROUP_LIST_SELECTORS.permissionGroupRow, - permissionName, - ) - .should("be.visible") - .find(BUTTON_SELECTORS.deleteIcon) - .click() - .get(BUTTON_SELECTORS.submit) - .click(); - cy.contains( - PERMISSION_GROUP_LIST_SELECTORS.permissionGroupRow, - permissionName, - ) - .should("not.exist") - .visit(staffMemberDetailsUrl(staffMember.id)) - .get(SHARED_ELEMENTS.header) - .should("be.visible") - .contains(permissionName) - .should("not.exist"); }); }, ); diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index b3fb24d30..649211ae7 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -7157,6 +7157,9 @@ "context": "label", "string": "External app" }, + "nBzIBG": { + "string": "Delete permission group" + }, "nEWp+k": { "context": "quantity of ordered products", "string": "Quantity" diff --git a/src/config.ts b/src/config.ts index 89e2d6f72..2ddca3bd3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -130,6 +130,7 @@ export const defaultListSettings: AppListViewSettings = { }, [ListViews.PERMISSION_GROUP_LIST]: { rowNumber: PAGINATE_BY, + columns: ["name", "members"], }, [ListViews.VOUCHER_LIST]: { rowNumber: PAGINATE_BY, diff --git a/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx index 6d2826e94..8e06922e7 100644 --- a/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx +++ b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx @@ -22,6 +22,7 @@ const props: PermissonGroupDetailsPageProps = { members: users, onAssign: () => undefined, onSort: () => undefined, + onDelete: () => undefined, onSubmit: () => new Promise(resolve => resolve(undefined)), onUnassign: () => undefined, permissionGroup, diff --git a/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx index e24ecd690..e882fa436 100644 --- a/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx +++ b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx @@ -66,6 +66,7 @@ export interface PermissonGroupDetailsPageProps permissionsExceeded: boolean; saveButtonBarState: "loading" | "success" | "error" | "default"; onAssign: () => void; + onDelete: () => void; onUnassign: (ids: string[]) => void; onSubmit: (data: PermissionGroupDetailsPageFormData) => SubmitPromise; } @@ -77,6 +78,7 @@ export const PermissionGroupDetailsPage: React.FC< errors, members, onSubmit, + onDelete, permissionGroup, permissions, permissionsExceeded, @@ -190,6 +192,7 @@ export const PermissionGroupDetailsPage: React.FC<
navigate(permissionGroupListUrl())} + onDelete={onDelete} onSubmit={submit} state={saveButtonBarState} disabled={disabled} diff --git a/src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx b/src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx deleted file mode 100644 index 204bb99e9..000000000 --- a/src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx +++ /dev/null @@ -1,176 +0,0 @@ -// @ts-strict-ignore -import ResponsiveTable from "@dashboard/components/ResponsiveTable"; -import Skeleton from "@dashboard/components/Skeleton"; -import { TableButtonWrapper } from "@dashboard/components/TableButtonWrapper/TableButtonWrapper"; -import TableCellHeader from "@dashboard/components/TableCellHeader"; -import { TablePaginationWithContext } from "@dashboard/components/TablePagination"; -import TableRowLink from "@dashboard/components/TableRowLink"; -import { PermissionGroupFragment } from "@dashboard/graphql"; -import { maybe, renderCollection, stopPropagation } from "@dashboard/misc"; -import { - permissionGroupDetailsUrl, - PermissionGroupListUrlSortField, -} from "@dashboard/permissionGroups/urls"; -import { ListProps, SortPage } from "@dashboard/types"; -import { getArrowDirection } from "@dashboard/utils/sort"; -import { - TableBody, - TableCell, - TableFooter, - TableHead, -} from "@material-ui/core"; -import { DeleteIcon, IconButton, makeStyles } from "@saleor/macaw-ui"; -import React from "react"; -import { FormattedMessage } from "react-intl"; - -const useStyles = makeStyles( - theme => ({ - [theme.breakpoints.up("lg")]: { - colActions: { - width: 180, - }, - colMembers: { - width: 180, - }, - colName: { - width: "auto", - }, - }, - colActions: { - paddingRight: theme.spacing(), - textAlign: "right", - }, - colActionsHeader: { - textAlign: "right", - }, - colMembers: { - textAlign: "right", - }, - colName: { - paddingLeft: 0, - }, - link: { - cursor: "pointer", - }, - }), - { name: "PermissionGroupList" }, -); -const numberOfColumns = 3; - -interface PermissionGroupListProps - extends ListProps, - SortPage { - permissionGroups: PermissionGroupFragment[]; - onDelete: (id: string) => void; -} - -const PermissionGroupList: React.FC = props => { - const { disabled, permissionGroups, onDelete, onSort, sort } = props; - const classes = useStyles(props); - - return ( - - - - onSort(PermissionGroupListUrlSortField.name)} - className={classes.colName} - > - - - - - - - - - - - - - - - - - {renderCollection( - permissionGroups, - permissionGroup => ( - permissionGroup.id)} - > - - {permissionGroup ? ( - {permissionGroup.name} - ) : ( - - )} - - - {permissionGroup ? ( - - {permissionGroup.users.length} - - ) : ( - - )} - - - {permissionGroup ? ( - <> - {permissionGroup.userCanManage && ( - - - onDelete(permissionGroup.id), - )} - > - - - - )} - - ) : ( - - )} - - - ), - () => ( - - - - - - ), - )} - - - ); -}; -PermissionGroupList.displayName = "PermissionGroupList"; -export default PermissionGroupList; diff --git a/src/permissionGroups/components/PermissionGroupList/index.ts b/src/permissionGroups/components/PermissionGroupList/index.ts deleted file mode 100644 index 9cfd81517..000000000 --- a/src/permissionGroups/components/PermissionGroupList/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./PermissionGroupList"; -export * from "./PermissionGroupList"; diff --git a/src/permissionGroups/components/PermissionGroupListDatagrid/PermissionGroupListDatagrid.tsx b/src/permissionGroups/components/PermissionGroupListDatagrid/PermissionGroupListDatagrid.tsx new file mode 100644 index 000000000..006d2b41a --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListDatagrid/PermissionGroupListDatagrid.tsx @@ -0,0 +1,136 @@ +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 { useEmptyColumn } from "@dashboard/components/Datagrid/hooks/useEmptyColumn"; +import { TablePaginationWithContext } from "@dashboard/components/TablePagination"; +import { PermissionGroupFragment } from "@dashboard/graphql"; +import useNavigator from "@dashboard/hooks/useNavigator"; +import { + permissionGroupDetailsUrl, + PermissionGroupListUrlSortField, +} from "@dashboard/permissionGroups/urls"; +import { canBeSorted } from "@dashboard/permissionGroups/views/PermissionGroupList/sort"; +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 { + createGetCellContent, + permissionGroupsListStaticColumnsAdapter, +} from "./datagrid"; +import { messages } from "./messages"; + +interface PermissionGroupListDatagridProps + extends ListProps, + SortPage { + permissionGroups: PermissionGroupFragment[]; +} + +export const PermissionGroupListDatagrid = ({ + disabled, + onSort, + permissionGroups, + sort, + settings, + onUpdateListSettings, +}: PermissionGroupListDatagridProps) => { + const intl = useIntl(); + const datagridState = useDatagridChangeState(); + const navigate = useNavigator(); + + const emptyColumn = useEmptyColumn(); + const permissionGroupsListStaticColumns = useMemo( + () => permissionGroupsListStaticColumnsAdapter(intl, sort, emptyColumn), + [intl, sort], + ); + + const onColumnChange = useCallback( + (picked: string[]) => { + if (onUpdateListSettings) { + onUpdateListSettings("columns", picked.filter(Boolean)); + } + }, + [onUpdateListSettings], + ); + + const { handlers, visibleColumns, recentlyAddedColumn } = useColumns({ + selectedColumns: settings?.columns ?? [], + staticColumns: permissionGroupsListStaticColumns, + onSave: onColumnChange, + }); + + const getCellContent = useCallback( + createGetCellContent({ + permissionGroups, + columns: visibleColumns, + }), + [permissionGroups, intl, visibleColumns], + ); + + const handleRowClick = useCallback( + ([_, row]: Item) => { + const rowData: PermissionGroupFragment = permissionGroups[row]; + + if (rowData) { + navigate(permissionGroupDetailsUrl(rowData.id)); + } + }, + [permissionGroups], + ); + + const handleRowAnchor = useCallback( + ([, row]: Item) => permissionGroupDetailsUrl(permissionGroups[row].id), + [permissionGroups], + ); + const handleHeaderClick = useCallback( + (col: number) => { + const columnName = visibleColumns[col] + .id as PermissionGroupListUrlSortField; + if (canBeSorted(columnName)) { + onSort(columnName); + } + }, + [visibleColumns, onSort], + ); + + return ( + + col > 1} + rows={permissionGroups?.length ?? 0} + availableColumns={visibleColumns} + emptyText={intl.formatMessage(messages.empty)} + getCellContent={getCellContent} + getCellError={() => false} + selectionActions={() => null} + menuItems={() => []} + onRowClick={handleRowClick} + onHeaderClicked={handleHeaderClick} + rowAnchor={handleRowAnchor} + recentlyAddedColumn={recentlyAddedColumn} + /> + + + + + + ); +}; diff --git a/src/permissionGroups/components/PermissionGroupListDatagrid/datagrid.ts b/src/permissionGroups/components/PermissionGroupListDatagrid/datagrid.ts new file mode 100644 index 000000000..41bd0224e --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListDatagrid/datagrid.ts @@ -0,0 +1,61 @@ +import { PLACEHOLDER } from "@dashboard/components/Datagrid/const"; +import { readonlyTextCell } from "@dashboard/components/Datagrid/customCells/cells"; +import { AvailableColumn } from "@dashboard/components/Datagrid/types"; +import { PermissionGroupFragment } from "@dashboard/graphql"; +import { PermissionGroupListUrlSortField } from "@dashboard/permissionGroups/urls"; +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 permissionGroupsListStaticColumnsAdapter = ( + intl: IntlShape, + sort: Sort, + emptyColumn: AvailableColumn, +) => + [ + emptyColumn, + { + id: "name", + title: intl.formatMessage(columnsMessages.name), + width: 450, + }, + { + id: "members", + title: intl.formatMessage(columnsMessages.members), + width: 200, + }, + ].map(column => ({ + ...column, + icon: getColumnSortDirectionIcon(sort, column.id), + })); + +export const createGetCellContent = + ({ + permissionGroups, + columns, + }: { + permissionGroups: PermissionGroupFragment[]; + columns: AvailableColumn[]; + }) => + ([column, row]: Item): GridCell => { + const rowData: PermissionGroupFragment | undefined = permissionGroups[row]; + const columnId = columns[column]?.id; + + if (!columnId || !rowData) { + return readonlyTextCell(""); + } + + switch (columnId) { + case "name": + return readonlyTextCell(rowData?.name ?? PLACEHOLDER); + case "members": + return readonlyTextCell( + rowData?.users?.length?.toString() ?? PLACEHOLDER, + ); + default: + return readonlyTextCell(""); + } + }; diff --git a/src/permissionGroups/components/PermissionGroupListDatagrid/index.ts b/src/permissionGroups/components/PermissionGroupListDatagrid/index.ts new file mode 100644 index 000000000..d915cfd9e --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListDatagrid/index.ts @@ -0,0 +1 @@ +export * from "./PermissionGroupListDatagrid"; diff --git a/src/permissionGroups/components/PermissionGroupListDatagrid/messages.ts b/src/permissionGroups/components/PermissionGroupListDatagrid/messages.ts new file mode 100644 index 000000000..f4bd6c29b --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListDatagrid/messages.ts @@ -0,0 +1,24 @@ +import { defineMessages } from "react-intl"; + +export const columnsMessages = defineMessages({ + name: { + id: "szXISP", + defaultMessage: "Permission Group Name", + description: "permission group name", + }, + members: { + id: "+a+2ug", + defaultMessage: "Members", + }, +}); + +export const messages = defineMessages({ + empty: { + id: "CXn88q", + defaultMessage: "No permission groups found", + }, + deletePermissionGroup: { + defaultMessage: "Delete permission group", + id: "nBzIBG", + }, +}); diff --git a/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx index 5995e52f0..e809a5202 100644 --- a/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx +++ b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx @@ -19,7 +19,6 @@ const props: PermissionGroupListPageProps = { ...pageListProps.default, ...sortPageProps, disabled: false, - onDelete: () => undefined, sort: { ...sortPageProps.sort, sort: PermissionGroupListUrlSortField.name, diff --git a/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx index b61845d41..4695f554f 100644 --- a/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx +++ b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx @@ -1,10 +1,11 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav"; -import { Button } from "@dashboard/components/Button"; import { ListPageLayout } from "@dashboard/components/Layouts"; import { configurationMenuUrl } from "@dashboard/configuration"; import { PermissionGroupFragment } from "@dashboard/graphql"; +import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; import { Card } from "@material-ui/core"; +import { Button } from "@saleor/macaw-ui/next"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -13,29 +14,30 @@ import { permissionGroupAddUrl, PermissionGroupListUrlSortField, } from "../../urls"; -import PermissionGroupList from "../PermissionGroupList"; +import { PermissionGroupListDatagrid } from "../PermissionGroupListDatagrid"; export interface PermissionGroupListPageProps extends PageListProps, SortPage { permissionGroups: PermissionGroupFragment[]; - onDelete: (id: string) => void; } const PermissionGroupListPage: React.FC< PermissionGroupListPageProps > = listProps => { const intl = useIntl(); + const navigate = useNavigator(); return (