Skip to content

Commit 5de2dbc

Browse files
committed
Add user assignment to tenant
1 parent 207adc7 commit 5de2dbc

20 files changed

+638
-6
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// TODO: copy from a project, validate it
2+
3+
import { useApolloClient } from "@apollo/client";
4+
import { CancelButton, Dialog, SaveBoundary, SaveBoundarySaveButton } from "@comet/admin";
5+
import { DialogActions, DialogContent } from "@mui/material";
6+
import { type PropsWithChildren, type ReactNode } from "react";
7+
import { FormattedMessage } from "react-intl";
8+
9+
type Props = {
10+
onDialogClose: () => void;
11+
open: boolean;
12+
apolloCacheName?: string;
13+
title?: ReactNode;
14+
};
15+
16+
export const AssignDialog = ({ onDialogClose, open, apolloCacheName, children, title }: PropsWithChildren<Props>) => {
17+
const client = useApolloClient();
18+
19+
return (
20+
<SaveBoundary
21+
onAfterSave={() => {
22+
onDialogClose();
23+
if (apolloCacheName) {
24+
client.cache.evict({ fieldName: apolloCacheName });
25+
}
26+
}}
27+
>
28+
<Dialog
29+
open={open}
30+
onClose={() => {
31+
onDialogClose();
32+
}}
33+
maxWidth="lg"
34+
title={title}
35+
>
36+
<DialogContent>{children}</DialogContent>
37+
38+
<DialogActions>
39+
<CancelButton
40+
onClick={() => {
41+
onDialogClose();
42+
}}
43+
/>
44+
<SaveBoundarySaveButton disabled={false}>
45+
<FormattedMessage id="common.assignSelection" defaultMessage="Assign selection" />
46+
</SaveBoundarySaveButton>
47+
</DialogActions>
48+
</Dialog>
49+
</SaveBoundary>
50+
);
51+
};

demo-saas/admin/src/tenants/TenantsPage.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useApolloClient } from "@apollo/client";
22
import {
3+
Button,
34
FieldSet,
45
FillSpace,
56
FullHeightContent,
@@ -17,13 +18,18 @@ import {
1718
ToolbarBackButton,
1819
useStackSwitch,
1920
} from "@comet/admin";
21+
import { Add } from "@comet/admin-icons";
2022
import { ContentScopeIndicator, useContentScopeConfig } from "@comet/cms-admin";
23+
import { AssignDialog } from "@src/common/AssignDialog";
24+
import { useState } from "react";
2125
import { FormattedMessage, useIntl } from "react-intl";
2226

2327
import { DepartmentForm } from "./departments/generated/DepartmentForm";
2428
import { DepartmentsGrid } from "./departments/generated/DepartmentsGrid";
2529
import { TenantForm } from "./generated/TenantForm";
2630
import { TenantsGrid } from "./generated/TenantsGrid";
31+
import { AssignTenantUser } from "./users/AssignTenantUser";
32+
import { TenantUsersGrid } from "./users/generated/TenantUsersGrid";
2733

2834
const FormToolbar = () => (
2935
<StackToolbar scopeIndicator={<ContentScopeIndicator global />}>
@@ -42,6 +48,7 @@ export function TenantsPage() {
4248
const [TenantsStackSwitch, tenantsStackSwitchApi] = useStackSwitch();
4349
const [DepartmentsStackSwitch, departmentsStackSwitchApi] = useStackSwitch();
4450
const client = useApolloClient();
51+
const [dialogOpen, setDialogOpen] = useState(false);
4552

4653
return (
4754
<Stack topLevelTitle={<FormattedMessage id="tenants.tenants" defaultMessage="Tenants" />}>
@@ -133,6 +140,26 @@ export function TenantsPage() {
133140
</StackPage>
134141
</DepartmentsStackSwitch>
135142
</RouterTab>
143+
<RouterTab path="/users" label={<FormattedMessage id="tenants.users" defaultMessage="Users" />}>
144+
<FullHeightContent>
145+
<TenantUsersGrid
146+
tenant={selectedTenantId}
147+
toolbarAction={
148+
<Button responsive startIcon={<Add />} onClick={() => setDialogOpen(true)}>
149+
<FormattedMessage id="tenants.user.assign" defaultMessage="Assign user" />
150+
</Button>
151+
}
152+
/>
153+
</FullHeightContent>
154+
<AssignDialog
155+
title={<FormattedMessage id="tenants.assignUsers.dialog.title" defaultMessage="Assign Users" />}
156+
apolloCacheName="tenantUsers"
157+
onDialogClose={() => setDialogOpen(false)}
158+
open={dialogOpen}
159+
>
160+
<AssignTenantUser tenantId={selectedTenantId} onDialogClose={() => setDialogOpen(false)} />
161+
</AssignDialog>
162+
</RouterTab>
136163
</RouterTabs>
137164
</StackMainContent>
138165
</>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { gql, useApolloClient } from "@apollo/client";
2+
import { MainContent, Savable } from "@comet/admin";
3+
import { useState } from "react";
4+
5+
import { type GQLCreateTenantUserMutation, type GQLCreateTenantUserMutationVariables } from "./AssignTenantUser.generated";
6+
import { UserPermissionsUsersGrid } from "./generated/AssignUsersGrid";
7+
8+
const createTenantUserQuery = gql`
9+
mutation CreateTenantUser($tenant: ID!, $userId: String!) {
10+
createTenantUser(tenant: $tenant, input: { userId: $userId }) {
11+
id
12+
}
13+
}
14+
`;
15+
16+
type AssignTenantUserProps = {
17+
tenantId: string;
18+
onDialogClose: () => void;
19+
};
20+
21+
export const AssignTenantUser = ({ tenantId, onDialogClose }: AssignTenantUserProps) => {
22+
const client = useApolloClient();
23+
const [values, setValues] = useState<string[]>([]);
24+
25+
return (
26+
<>
27+
<Savable
28+
doSave={async () => {
29+
for (const userId of values) {
30+
await client.mutate<GQLCreateTenantUserMutation, GQLCreateTenantUserMutationVariables>({
31+
mutation: createTenantUserQuery,
32+
variables: { tenant: tenantId, userId },
33+
});
34+
}
35+
return true;
36+
}}
37+
hasChanges={values.length > 0}
38+
doReset={() => {
39+
setValues([]);
40+
}}
41+
/>
42+
<MainContent disablePadding sx={{ height: 500 }}>
43+
<UserPermissionsUsersGrid
44+
rowSelectionModel={values}
45+
onRowSelectionModelChange={(newUserSelection) => {
46+
setValues(newUserSelection.map((rowId) => String(rowId)));
47+
}}
48+
/>
49+
</MainContent>
50+
</>
51+
);
52+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { defineConfig } from "@comet/admin-generator";
2+
import { type GQLUserPermissionsUser } from "@src/graphql.generated";
3+
4+
export default defineConfig<GQLUserPermissionsUser>({
5+
type: "grid",
6+
gqlType: "UserPermissionsUser",
7+
fragmentName: "AssignTenantUsersGrid",
8+
queryParamsPrefix: "assignTenantUsers",
9+
selectionProps: "multiSelect",
10+
add: false,
11+
edit: false,
12+
delete: false,
13+
columns: [{ type: "text", name: "name", headerName: "Name" }],
14+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { defineConfig } from "@comet/admin-generator";
2+
import { type GQLTenantUser } from "@src/graphql.generated";
3+
4+
export default defineConfig<GQLTenantUser>({
5+
type: "grid",
6+
gqlType: "TenantUser",
7+
fragmentName: "TenantUsersGrid",
8+
queryParamsPrefix: "tenantUsers",
9+
newEntryText: "Assign User",
10+
edit: false,
11+
toolbarActionProp: true,
12+
columns: [
13+
{ type: "text", name: "userName", headerName: "User" },
14+
{ type: "text", name: "tenant.name", headerName: "Tenant" },
15+
],
16+
});

demo-saas/admin/src/tenants/users/generated/AssignUsersGrid.tsx

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-saas/admin/src/tenants/users/generated/TenantUsersGrid.tsx

Lines changed: 113 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)