Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use Datagrid for display and select scopes instead of formField checkboxList #3147

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
---

Exchange Content Scope FormField Checkbox List with Datagrid view and selection
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
import { gql, useApolloClient, useQuery } from "@apollo/client";
import { Field, FinalForm, FinalFormCheckbox, Loading, SaveButton, ToolbarActions, ToolbarFillSpace, ToolbarTitleItem } from "@comet/admin";
import { Card, CardContent, Toolbar } from "@mui/material";
import { gql, useQuery } from "@apollo/client";
import { GridColDef, Loading, ToolbarActions, ToolbarFillSpace, ToolbarTitleItem } from "@comet/admin";
import { Select } from "@comet/admin-icons";
import { Button, Card, CardContent, Toolbar, Typography } from "@mui/material";
import { styled } from "@mui/material/styles";
import isEqual from "lodash.isequal";
import { FormattedMessage } from "react-intl";
import { DataGrid } from "@mui/x-data-grid";
import { useState } from "react";
import { FormattedMessage, IntlShape, useIntl } from "react-intl";

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

type FormValues = {
contentScopes: string[];
};
type ContentScope = {
[key: string]: string;
};
import { GQLContentScopesQuery, GQLContentScopesQueryVariables } from "./ContentScopeGrid.generated";
import { SelectScopesDialog } from "./selectScopesDialog/SelectScopesDialog";

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 intl = useIntl();
const [open, setOpen] = useState(false);

const { data, error } = useQuery<GQLContentScopesQuery, GQLContentScopesQueryVariables>(
gql`
Expand All @@ -53,57 +28,73 @@ export const ContentScopeGrid = ({ userId }: { userId: string }) => {
},
);

const columns: GridColDef<{ [key in string]: string }>[] = generateGridColumnsFromContentScopeProperties(data?.userContentScopes || [], intl);

if (error) throw new Error(error.message);

if (!data) {
return <Loading />;
}

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>
<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>
{data.availableContentScopes.map((contentScope: ContentScope) => (
<Field
disabled={data.userContentScopesSkipManual.some((cs: ContentScope) => isEqual(cs, contentScope))}
key={JSON.stringify(contentScope)}
name="contentScopes"
fullWidth
variant="horizontal"
type="checkbox"
component={FinalFormCheckbox}
value={JSON.stringify(contentScope)}
label={Object.entries(contentScope).map(([scope, value]) => (
<>
{camelCaseToHumanReadable(scope)}: {camelCaseToHumanReadable(value)}
<br />
</>
))}
/>
))}
<DataGrid
autoHeight={true}
rows={data.userContentScopes ?? []}
columns={columns}
rowCount={data?.userContentScopes.length ?? 0}
loading={false}
getRowHeight={() => "auto"}
getRowId={(row) => JSON.stringify(row)}
sx={{ "&.MuiDataGrid-root .MuiDataGrid-cell": { py: "8px" } }}
/>
</CardContent>
</FinalForm>
</Card>
</Card>
<SelectScopesDialog open={open} onClose={() => setOpen(false)} data={data} userId={userId} />
</>
);
};

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

export function generateGridColumnsFromContentScopeProperties(
data: GQLContentScopesQuery["userContentScopes"] | GQLContentScopesQuery["availableContentScopes"],
intl: IntlShape,
): 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: false,
headerName: camelCaseToHumanReadable(
intl.formatMessage({ id: `comet.userPermissions.contentScope.${propertyName}`, defaultMessage: 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
@@ -0,0 +1,135 @@
import { useApolloClient } from "@apollo/client";
import {
CancelButton,
DataGridToolbar,
Field,
FinalForm,
SaveBoundary,
SaveBoundarySaveButton,
ToolbarFillSpace,
ToolbarItem,
useFormApiRef,
} from "@comet/admin";
import { Dialog, DialogActions, DialogTitle } from "@mui/material";
import { DataGrid, GridColDef, GridToolbarQuickFilter } from "@mui/x-data-grid";
import gql from "graphql-tag";
import { FormattedMessage, useIntl } from "react-intl";

import { generateGridColumnsFromContentScopeProperties } from "../ContentScopeGrid";
import { GQLContentScopesQuery } from "../ContentScopeGrid.generated";
import { GQLUpdateContentScopesMutation, GQLUpdateContentScopesMutationVariables } from "./SelectScopesDialog.generated";

export interface SelectScopesDialogProps {
open: boolean;
onClose: () => void;
data: GQLContentScopesQuery;
userId: string;
}

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

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

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

export const SelectScopesDialog: React.FunctionComponent<React.PropsWithChildren<SelectScopesDialogProps>> = ({ open, onClose, data, userId }) => {
const client = useApolloClient();
const intl = useIntl();
const formApiRef = useFormApiRef<FormValues>();
const submit = async (values: FormValues) => {
await client.mutate<GQLUpdateContentScopesMutation, GQLUpdateContentScopesMutationVariables>({
mutation: gql`
mutation UpdateContentScopes($userId: String!, $input: UserContentScopesInput!) {
userPermissionsUpdateContentScopes(userId: $userId, input: $input)
}
`,
variables: {
userId,
input: {
contentScopes: values.contentScopes.map((contentScope) => JSON.parse(contentScope)),
},
},
refetchQueries: ["ContentScopes"],
});
};

const columns: GridColDef<{ [key in string]: string }>[] = generateGridColumnsFromContentScopeProperties(
data?.availableContentScopes || [],
intl,
);

return (
<SaveBoundary
onAfterSave={() => {
onClose();
}}
>
<Dialog open={open} maxWidth="lg">
<DialogTitle>
<FormattedMessage id="comet.userScopes.dialog.title" defaultMessage="Select scopes" />
</DialogTitle>
<FinalForm<FormValues>
apiRef={formApiRef}
subscription={{ values: true }}
mode="edit"
onSubmit={submit}
onAfterSubmit={() => null}
initialValues={{
contentScopes: data.userContentScopes.map((cs) => JSON.stringify(cs)),
}}
>
<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 ?? 0}
loading={false}
getRowHeight={() => "auto"}
getRowId={(row) => JSON.stringify(row)}
isRowSelectable={(params) => {
return !data.userContentScopesSkipManual.some(
(cs: ContentScope) => JSON.stringify(cs) === JSON.stringify(params.row),
);
}}
checkboxSelection
sx={{ "&.MuiDataGrid-root .MuiDataGrid-cell": { py: "8px" }, width: "100%" }}
selectionModel={props.input.value}
onSelectionModelChange={(selectionModel) => {
props.input.onChange(selectionModel.map((id) => String(id)));
}}
components={{
Toolbar: SelectScopesGridToolbar,
}}
/>
);
}}
</Field>
</FinalForm>
<DialogActions>
<CancelButton onClick={onClose}>
<FormattedMessage id="comet.userScopes.close" defaultMessage="Close" />
</CancelButton>
<SaveBoundarySaveButton />
</DialogActions>
</Dialog>
</SaveBoundary>
);
};