Skip to content

Commit

Permalink
User Permissions: Use Data Grid instead of a checkbox list for displa…
Browse files Browse the repository at this point in the history
…ying and selecting content scopes (#3147)
  • Loading branch information
MFSepplive authored Feb 12, 2025
1 parent 2d248bb commit 97cd0a3
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 80 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-fireants-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@comet/cms-admin": minor
---

User Permissions: Use Data Grid instead of a checkbox list for displaying and selecting content scopes
Original file line number Diff line number Diff line change
@@ -1,49 +1,37 @@
import { gql, useApolloClient, useQuery } from "@apollo/client";
import { CheckboxListField, FillSpace, FinalForm, Loading, SaveButton, ToolbarActions, ToolbarTitleItem } from "@comet/admin";
import { Card, CardContent, Toolbar } from "@mui/material";
import { gql, useQuery } from "@apollo/client";
import {
CancelButton,
GridColDef,
Loading,
messages,
SaveBoundary,
SaveBoundarySaveButton,
ToolbarActions,
ToolbarFillSpace,
ToolbarTitleItem,
} from "@comet/admin";
import { Select } from "@comet/admin-icons";
import { Button, Card, CardContent, Dialog, DialogActions, DialogTitle, Toolbar, Typography } from "@mui/material";
import { styled } from "@mui/material/styles";
import isEqual from "lodash.isequal";
import { DataGrid } from "@mui/x-data-grid";
import { useState } from "react";
import { FormattedMessage } from "react-intl";

import { camelCaseToHumanReadable } from "../../utils/camelCaseToHumanReadable";
import {
GQLContentScopesQuery,
GQLContentScopesQueryVariables,
GQLUpdateContentScopesMutation,
GQLUpdateContentScopesMutationVariables,
} from "./ContentScopeGrid.generated";
import { GQLContentScopesQuery, GQLContentScopesQueryVariables } from "./ContentScopeGrid.generated";
import { SelectScopesDialogContent } from "./selectScopesDialogContent/SelectScopesDialogContent";
import { GQLAvailableContentScopesQuery } from "./selectScopesDialogContent/SelectScopesDialogContent.generated";

type FormValues = {
contentScopes: string[];
};
type ContentScope = {
[key: string]: string;
};

export const ContentScopeGrid = ({ userId }: { userId: string }) => {
const client = useApolloClient();

const submit = async (data: FormValues) => {
await client.mutate<GQLUpdateContentScopesMutation, GQLUpdateContentScopesMutationVariables>({
mutation: gql`
mutation UpdateContentScopes($userId: String!, $input: UserContentScopesInput!) {
userPermissionsUpdateContentScopes(userId: $userId, input: $input)
}
`,
variables: {
userId,
input: {
contentScopes: data.contentScopes.map((contentScope) => JSON.parse(contentScope)),
},
},
refetchQueries: ["ContentScopes"],
});
};
const [open, setOpen] = useState(false);

const { data, error } = useQuery<GQLContentScopesQuery, GQLContentScopesQueryVariables>(
gql`
query ContentScopes($userId: String!) {
availableContentScopes: userPermissionsAvailableContentScopes
userContentScopes: userPermissionsContentScopes(userId: $userId)
userContentScopesSkipManual: userPermissionsContentScopes(userId: $userId, skipManual: true)
}
Expand All @@ -58,48 +46,85 @@ export const ContentScopeGrid = ({ userId }: { userId: string }) => {
if (!data) {
return <Loading />;
}

const columns: GridColDef<ContentScope>[] = generateGridColumnsFromContentScopeProperties(data.userContentScopes);

return (
<Card>
<FinalForm<FormValues>
mode="edit"
onSubmit={submit}
onAfterSubmit={() => null}
initialValues={{ contentScopes: data.userContentScopes.map((cs) => JSON.stringify(cs)) }}
>
<>
<Card>
<CardToolbar>
<ToolbarTitleItem>
<FormattedMessage id="comet.userPermissions.scopes" defaultMessage="Scopes" />
</ToolbarTitleItem>
<FillSpace />
<ToolbarFillSpace />
<ToolbarActions>
<SaveButton type="submit">
<FormattedMessage id="comet.userPermissions.save" defaultMessage="Save" />
</SaveButton>
<Button startIcon={<Select />} onClick={() => setOpen(true)} variant="contained" color="primary">
<FormattedMessage id="comet.userPermissions.selectScopes" defaultMessage="Select scopes" />
</Button>
</ToolbarActions>
</CardToolbar>
<CardContent>
<CheckboxListField
fullWidth
name="contentScopes"
layout="column"
options={data.availableContentScopes.map((contentScope: ContentScope) => ({
label: Object.entries(contentScope).map(([scope, value]) => (
<>
{camelCaseToHumanReadable(scope)}: {camelCaseToHumanReadable(value)}
<br />
</>
)),
value: JSON.stringify(contentScope),
disabled: data.userContentScopesSkipManual.some((cs: ContentScope) => isEqual(cs, contentScope)),
}))}
<DataGrid
autoHeight={true}
rows={data.userContentScopes ?? []}
columns={columns}
rowCount={data?.userContentScopes.length ?? 0}
loading={false}
getRowHeight={() => "auto"}
getRowId={(row) => JSON.stringify(row)}
/>
</CardContent>
</FinalForm>
</Card>
</Card>
<SaveBoundary
onAfterSave={() => {
setOpen(false);
}}
>
<Dialog open={open} maxWidth="lg">
<DialogTitle>
<FormattedMessage id="comet.userScopes.dialog.title" defaultMessage="Select scopes" />
</DialogTitle>
<SelectScopesDialogContent
userId={userId}
userContentScopes={data.userContentScopes}
userContentScopesSkipManual={data.userContentScopesSkipManual}
/>
<DialogActions>
<CancelButton onClick={() => setOpen(false)}>
<FormattedMessage {...messages.close} />
</CancelButton>
<SaveBoundarySaveButton />
</DialogActions>
</Dialog>
</SaveBoundary>
</>
);
};

const CardToolbar = styled(Toolbar)`
top: 0px;
border-bottom: 1px solid ${({ theme }) => theme.palette.grey[100]};
`;

export function generateGridColumnsFromContentScopeProperties(
data: GQLContentScopesQuery["userContentScopes"] | GQLAvailableContentScopesQuery["availableContentScopes"],
): GridColDef[] {
const uniquePropertyNames = Array.from(new Set(data.flatMap((item) => Object.keys(item))));
return uniquePropertyNames.map((propertyName) => {
return {
field: propertyName,
flex: 1,
pinnable: false,
sortable: false,
filterable: true,
headerName: camelCaseToHumanReadable(propertyName),
renderCell: ({ row }) => {
if (row[propertyName] != null) {
return <Typography variant="subtitle2">{camelCaseToHumanReadable(row[propertyName])}</Typography>;
} else {
return "-";
}
},
};
});
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { gql, useApolloClient, useQuery } from "@apollo/client";
import { CancelButton, CheckboxListField, Field, FinalForm, FinalFormSwitch, SaveButton } from "@comet/admin";
import { CancelButton, DataGridToolbar, Field, FinalForm, FinalFormSwitch, SaveButton, ToolbarFillSpace, ToolbarItem } from "@comet/admin";
import { CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
import { DataGrid, GridColDef, GridToolbarQuickFilter } from "@mui/x-data-grid";
import isEqual from "lodash.isequal";
import { FormattedMessage } from "react-intl";

import { camelCaseToHumanReadable } from "../../utils/camelCaseToHumanReadable";
import { generateGridColumnsFromContentScopeProperties } from "./ContentScopeGrid";
import {
GQLOverrideContentScopesMutation,
GQLOverrideContentScopesMutationVariables,
Expand All @@ -25,6 +27,17 @@ type ContentScope = {
[key: string]: string;
};

function OverrideContentScopesDialogGridToolbar() {
return (
<DataGridToolbar>
<ToolbarItem>
<GridToolbarQuickFilter />
</ToolbarItem>
<ToolbarFillSpace />
</DataGridToolbar>
);
}

export const OverrideContentScopesDialog = ({ permissionId, userId, handleDialogClose }: FormProps) => {
const client = useApolloClient();

Expand All @@ -51,13 +64,14 @@ export const OverrideContentScopesDialog = ({ permissionId, userId, handleDialog

const { data, error } = useQuery<GQLPermissionContentScopesQuery, GQLPermissionContentScopesQueryVariables>(
gql`
query PermissionContentScopes($permissionId: ID!, $userId: String) {
query PermissionContentScopes($permissionId: ID!, $userId: String!) {
availableContentScopes: userPermissionsAvailableContentScopes
permission: userPermissionsPermission(id: $permissionId, userId: $userId) {
source
overrideContentScopes
contentScopes
}
userContentScopesSkipManual: userPermissionsContentScopes(userId: $userId, skipManual: true)
}
`,
{
Expand All @@ -79,8 +93,10 @@ export const OverrideContentScopesDialog = ({ permissionId, userId, handleDialog
};
const disabled = data && data.permission.source === "BY_RULE";

const columns: GridColDef<ContentScope>[] = generateGridColumnsFromContentScopeProperties(data.availableContentScopes);

return (
<Dialog maxWidth="sm" open={true}>
<Dialog maxWidth="lg" open={true}>
<FinalForm<FormSubmitData>
mode="edit"
onSubmit={submit}
Expand All @@ -101,22 +117,37 @@ export const OverrideContentScopesDialog = ({ permissionId, userId, handleDialog
disabled={disabled}
/>
{values.overrideContentScopes && (
<CheckboxListField
fullWidth
name="contentScopes"
variant="horizontal"
layout="column"
options={data.availableContentScopes.map((contentScope: ContentScope) => ({
label: Object.entries(contentScope).map(([scope, value]) => (
<>
{camelCaseToHumanReadable(scope)}: {camelCaseToHumanReadable(value)}
<br />
</>
)),
value: JSON.stringify(contentScope),
disabled: disabled,
}))}
/>
<Field name="contentScopes" fullWidth>
{(props) => {
return (
<DataGrid
autoHeight={true}
rows={
data.availableContentScopes.filter(
(obj) => !Object.values(obj).every((value) => value === undefined),
) ?? []
}
columns={columns}
rowCount={data.availableContentScopes.length}
loading={false}
getRowHeight={() => "auto"}
getRowId={(row) => JSON.stringify(row)}
checkboxSelection={!disabled}
selectionModel={props.input.value}
onSelectionModelChange={(selectionModel) => {
props.input.onChange(selectionModel.map((id) => String(id)));
}}
components={{
Toolbar: OverrideContentScopesDialogGridToolbar,
}}
pageSize={25}
isRowSelectable={(params) => {
return !data.userContentScopesSkipManual.some((cs: ContentScope) => isEqual(cs, params.row));
}}
/>
);
}}
</Field>
)}
</DialogContent>
<DialogActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ export const PermissionGrid = ({ userId }: { userId: string }) => {
rowCount={data?.permissions.length ?? 0}
loading={loading}
getRowHeight={() => "auto"}
sx={{ "&.MuiDataGrid-root .MuiDataGrid-cell": { py: "8px" } }}
components={{
Toolbar: () => (
<GridToolbar>
Expand Down
Loading

0 comments on commit 97cd0a3

Please sign in to comment.