29
common/resources/client/admin/roles/crupdate-role-page/create-role-page.tsx
Executable file
29
common/resources/client/admin/roles/crupdate-role-page/create-role-page.tsx
Executable file
@@ -0,0 +1,29 @@
|
||||
import {useForm} from 'react-hook-form';
|
||||
import {CrupdateResourceLayout} from '../../crupdate-resource-layout';
|
||||
import {Trans} from '../../../i18n/trans';
|
||||
import {CrupdateRolePageSettingsPanel} from './crupdate-role-settings-panel';
|
||||
import {CreateRolePayload, useCreateRole} from '../requests/user-create-role';
|
||||
import {useNavigate} from '../../../utils/hooks/use-navigate';
|
||||
|
||||
export function CreateRolePage() {
|
||||
const form = useForm<CreateRolePayload>({defaultValues: {type: 'sitewide'}});
|
||||
const createRole = useCreateRole(form);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<CrupdateResourceLayout
|
||||
form={form}
|
||||
onSubmit={values => {
|
||||
createRole.mutate(values, {
|
||||
onSuccess: response => {
|
||||
navigate(`/admin/roles/${response.role.id}/edit`);
|
||||
},
|
||||
});
|
||||
}}
|
||||
title={<Trans message="Add new role" />}
|
||||
isLoading={createRole.isPending}
|
||||
>
|
||||
<CrupdateRolePageSettingsPanel />
|
||||
</CrupdateResourceLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import {Role} from '../../../auth/role';
|
||||
import {useTrans} from '../../../i18n/use-trans';
|
||||
import {useFormContext} from 'react-hook-form';
|
||||
import {FormTextField} from '../../../ui/forms/input-field/text-field/text-field';
|
||||
import {Trans} from '../../../i18n/trans';
|
||||
import {message} from '../../../i18n/message';
|
||||
import {FormSelect} from '../../../ui/forms/select/select';
|
||||
import {Item} from '../../../ui/forms/listbox/item';
|
||||
import {FormSwitch} from '../../../ui/forms/toggle/switch';
|
||||
import {FormPermissionSelector} from '../../../auth/ui/permission-selector';
|
||||
import {useSettings} from '../../../core/settings/use-settings';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
|
||||
interface CrupdateRolePageSettingsPanelProps {
|
||||
isInternal?: boolean;
|
||||
}
|
||||
export function CrupdateRolePageSettingsPanel({
|
||||
isInternal = false,
|
||||
}: CrupdateRolePageSettingsPanelProps) {
|
||||
const {trans} = useTrans();
|
||||
const {workspaces} = useSettings();
|
||||
const {watch, setValue} = useFormContext<Role>();
|
||||
const watchedType = watch('type');
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormTextField
|
||||
label={<Trans message="Name" />}
|
||||
name="name"
|
||||
className="mb-20"
|
||||
required
|
||||
/>
|
||||
<FormTextField
|
||||
label={<Trans message="Description" />}
|
||||
name="description"
|
||||
inputElementType="textarea"
|
||||
placeholder={trans(message('Role description...'))}
|
||||
rows={4}
|
||||
className="mb-20"
|
||||
/>
|
||||
{workspaces.integrated && (
|
||||
<FormSelect
|
||||
label={<Trans message="Type" />}
|
||||
name="type"
|
||||
selectionMode="single"
|
||||
className="mb-20"
|
||||
description={
|
||||
<Trans message="Whether this role will be assigned to users globally on the site or only within workspaces." />
|
||||
}
|
||||
>
|
||||
<Item value="sitewide">
|
||||
<Trans message="Sitewide" />
|
||||
</Item>
|
||||
<Item value="workspace">
|
||||
<Trans message="Workspace" />
|
||||
</Item>
|
||||
</FormSelect>
|
||||
)}
|
||||
{!isInternal && (
|
||||
<>
|
||||
<FormSwitch
|
||||
name="default"
|
||||
className="mb-20"
|
||||
description={
|
||||
<Trans message="Assign this role to new users automatically." />
|
||||
}
|
||||
>
|
||||
<Trans message="Default" />
|
||||
</FormSwitch>
|
||||
{watchedType === 'sitewide' && (
|
||||
<FormSwitch
|
||||
name="guests"
|
||||
description={
|
||||
<Trans message="Assign this role to guests (not logged in users)." />
|
||||
}
|
||||
>
|
||||
<Trans message="Guests" />
|
||||
</FormSwitch>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="mb-14 mt-30 flex items-end justify-between gap-12">
|
||||
<h2 className="text-lg leading-tight">
|
||||
<Trans message="Permissions" />
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={() => setValue('permissions', [])}
|
||||
>
|
||||
<Trans message="Remove all" />
|
||||
</Button>
|
||||
</div>
|
||||
<FormPermissionSelector
|
||||
name="permissions"
|
||||
valueListKey={
|
||||
watchedType === 'sitewide' ? 'permissions' : 'workspacePermissions'
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import {Role} from '../../../auth/role';
|
||||
import {ColumnConfig} from '../../../datatable/column-config';
|
||||
import {User} from '../../../auth/user';
|
||||
import {Trans} from '../../../i18n/trans';
|
||||
import {NameWithAvatar} from '../../../datatable/column-templates/name-with-avatar';
|
||||
import {FormattedDate} from '../../../i18n/formatted-date';
|
||||
import React from 'react';
|
||||
import teamSvg from '../team.svg';
|
||||
import {DialogTrigger} from '../../../ui/overlays/dialog/dialog-trigger';
|
||||
import {Button} from '../../../ui/buttons/button';
|
||||
import {SelectUserDialog} from '../../../users/select-user-dialog';
|
||||
import {queryClient} from '../../../http/query-client';
|
||||
import {DatatableDataQueryKey} from '../../../datatable/requests/paginated-resources';
|
||||
import {DataTableEmptyStateMessage} from '../../../datatable/page/data-table-emty-state-message';
|
||||
import {useDataTable} from '../../../datatable/page/data-table-context';
|
||||
import {ConfirmationDialog} from '../../../ui/overlays/dialog/confirmation-dialog';
|
||||
import {useRemoveUsersFromRole} from '../requests/use-remove-users-from-role';
|
||||
import {useAddUsersToRole} from '../requests/use-add-users-to-role';
|
||||
import {DataTable} from '../../../datatable/data-table';
|
||||
import {useIsMobileMediaQuery} from '../../../utils/hooks/is-mobile-media-query';
|
||||
|
||||
const userColumn: ColumnConfig<User> = {
|
||||
key: 'name',
|
||||
allowsSorting: true,
|
||||
sortingKey: 'email',
|
||||
header: () => <Trans message="User" />,
|
||||
body: user => (
|
||||
<NameWithAvatar
|
||||
image={user.avatar}
|
||||
label={user.display_name}
|
||||
description={user.email}
|
||||
/>
|
||||
),
|
||||
width: 'col-w-3',
|
||||
};
|
||||
|
||||
const desktopColumns: ColumnConfig<User>[] = [
|
||||
userColumn,
|
||||
{
|
||||
key: 'first_name',
|
||||
allowsSorting: true,
|
||||
header: () => <Trans message="First name" />,
|
||||
body: user => user.first_name,
|
||||
},
|
||||
{
|
||||
key: 'last_name',
|
||||
allowsSorting: true,
|
||||
header: () => <Trans message="Last name" />,
|
||||
body: user => user.last_name,
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
allowsSorting: true,
|
||||
header: () => <Trans message="Assigned at" />,
|
||||
body: user => <FormattedDate date={user.created_at} />,
|
||||
},
|
||||
];
|
||||
|
||||
const mobileColumns: ColumnConfig<User>[] = [userColumn];
|
||||
|
||||
interface CrupdateRolePageUsersPanelProps {
|
||||
role: Role;
|
||||
}
|
||||
export function EditRolePageUsersPanel({
|
||||
role,
|
||||
}: CrupdateRolePageUsersPanelProps) {
|
||||
const isMobile = useIsMobileMediaQuery();
|
||||
|
||||
if (role.guests || role.type === 'workspace') {
|
||||
return (
|
||||
<div className="pt-30 pb-10">
|
||||
<DataTableEmptyStateMessage
|
||||
image={teamSvg}
|
||||
title={<Trans message="Users can't be assigned to this role" />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
endpoint="users"
|
||||
columns={isMobile ? mobileColumns : desktopColumns}
|
||||
queryParams={{roleId: `${role.id}`}}
|
||||
actions={<AssignUserAction role={role} />}
|
||||
selectedActions={<RemoveUsersAction role={role} />}
|
||||
emptyStateMessage={
|
||||
<DataTableEmptyStateMessage
|
||||
image={teamSvg}
|
||||
title={
|
||||
<Trans message="No users have been assigned to this role yet" />
|
||||
}
|
||||
filteringTitle={<Trans message="No matching users" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface AssignUserActionProps {
|
||||
role: Role;
|
||||
}
|
||||
function AssignUserAction({role}: AssignUserActionProps) {
|
||||
const addUsers = useAddUsersToRole(role);
|
||||
return (
|
||||
<DialogTrigger type="modal">
|
||||
<Button variant="flat" color="primary" disabled={addUsers.isPending}>
|
||||
<Trans message="Assign user" />
|
||||
</Button>
|
||||
<SelectUserDialog
|
||||
onUserSelected={user => {
|
||||
addUsers.mutate(
|
||||
{userIds: [user.id as number]},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: DatatableDataQueryKey('users', {
|
||||
roleId: `${role.id}`,
|
||||
}),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
type RemoveUsersActionProps = {
|
||||
role: Role;
|
||||
};
|
||||
export function RemoveUsersAction({role}: RemoveUsersActionProps) {
|
||||
const removeUsers = useRemoveUsersFromRole(role);
|
||||
const {selectedRows} = useDataTable();
|
||||
|
||||
return (
|
||||
<DialogTrigger
|
||||
type="modal"
|
||||
onClose={isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
removeUsers.mutate(
|
||||
{userIds: selectedRows as number[]},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: DatatableDataQueryKey('users', {
|
||||
roleId: `${role.id}`,
|
||||
}),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button variant="flat" color="danger" disabled={removeUsers.isPending}>
|
||||
<Trans message="Remove users" />
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
title={
|
||||
<Trans
|
||||
message="Remove [one 1 user|other :count users] from “:name“ role?"
|
||||
values={{count: selectedRows.length, name: role.name}}
|
||||
/>
|
||||
}
|
||||
body={<Trans message="This will permanently remove the users." />}
|
||||
confirm={<Trans message="Remove" />}
|
||||
isDanger
|
||||
/>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
61
common/resources/client/admin/roles/crupdate-role-page/edit-role-page.tsx
Executable file
61
common/resources/client/admin/roles/crupdate-role-page/edit-role-page.tsx
Executable file
@@ -0,0 +1,61 @@
|
||||
import {useRole} from '../requests/use-role';
|
||||
import {FullPageLoader} from '../../../ui/progress/full-page-loader';
|
||||
import {Role} from '../../../auth/role';
|
||||
import {Trans} from '../../../i18n/trans';
|
||||
import {useForm} from 'react-hook-form';
|
||||
import {Tabs} from '../../../ui/tabs/tabs';
|
||||
import {Tab} from '../../../ui/tabs/tab';
|
||||
import {TabList} from '../../../ui/tabs/tab-list';
|
||||
import {TabPanel, TabPanels} from '../../../ui/tabs/tab-panels';
|
||||
import {useUpdateRole} from '../requests/use-update-role';
|
||||
import {CrupdateResourceLayout} from '../../crupdate-resource-layout';
|
||||
import {CrupdateRolePageSettingsPanel} from './crupdate-role-settings-panel';
|
||||
import {EditRolePageUsersPanel} from './edit-role-page-users-panel';
|
||||
|
||||
export function EditRolePage() {
|
||||
const query = useRole();
|
||||
|
||||
if (query.status !== 'success') {
|
||||
return <FullPageLoader />;
|
||||
}
|
||||
|
||||
return <PageContent role={query.data.role} />;
|
||||
}
|
||||
|
||||
interface PageContentProps {
|
||||
role: Role;
|
||||
}
|
||||
function PageContent({role}: PageContentProps) {
|
||||
const form = useForm<Role>({defaultValues: role});
|
||||
const updateRole = useUpdateRole();
|
||||
|
||||
return (
|
||||
<CrupdateResourceLayout
|
||||
form={form}
|
||||
onSubmit={values => {
|
||||
updateRole.mutate(values);
|
||||
}}
|
||||
title={<Trans message="Edit “:name“ role" values={{name: role.name}} />}
|
||||
isLoading={updateRole.isPending}
|
||||
>
|
||||
<Tabs isLazy>
|
||||
<TabList>
|
||||
<Tab>
|
||||
<Trans message="Settings" />
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Trans message="Users" />
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanels className="pt-20">
|
||||
<TabPanel>
|
||||
<CrupdateRolePageSettingsPanel isInternal={role.internal} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<EditRolePageUsersPanel role={role} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</CrupdateResourceLayout>
|
||||
);
|
||||
}
|
||||
37
common/resources/client/admin/roles/requests/use-add-users-to-role.ts
Executable file
37
common/resources/client/admin/roles/requests/use-add-users-to-role.ts
Executable file
@@ -0,0 +1,37 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {apiClient} from '../../../http/query-client';
|
||||
import {BackendResponse} from '../../../http/backend-response/backend-response';
|
||||
import {toast} from '../../../ui/toast/toast';
|
||||
import {message} from '../../../i18n/message';
|
||||
import {Role} from '../../../auth/role';
|
||||
import {showHttpErrorToast} from '../../../utils/http/show-http-error-toast';
|
||||
|
||||
interface Response extends BackendResponse {}
|
||||
|
||||
interface Payload {
|
||||
userIds: number[];
|
||||
}
|
||||
|
||||
export function useAddUsersToRole(role: Role) {
|
||||
return useMutation({
|
||||
mutationFn: ({userIds}: Payload) =>
|
||||
addUsersToRole({userIds, roleId: role.id}),
|
||||
onSuccess: (response, payload) => {
|
||||
toast(
|
||||
message('Assigned [one 1 user|other :count users] to {role}', {
|
||||
values: {count: payload.userIds.length, role: role.name},
|
||||
}),
|
||||
);
|
||||
},
|
||||
onError: err => showHttpErrorToast(err),
|
||||
});
|
||||
}
|
||||
|
||||
function addUsersToRole({
|
||||
roleId,
|
||||
userIds,
|
||||
}: Payload & {roleId: number}): Promise<Response> {
|
||||
return apiClient
|
||||
.post(`roles/${roleId}/add-users`, {userIds})
|
||||
.then(r => r.data);
|
||||
}
|
||||
37
common/resources/client/admin/roles/requests/use-remove-users-from-role.ts
Executable file
37
common/resources/client/admin/roles/requests/use-remove-users-from-role.ts
Executable file
@@ -0,0 +1,37 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {apiClient} from '../../../http/query-client';
|
||||
import {BackendResponse} from '../../../http/backend-response/backend-response';
|
||||
import {toast} from '../../../ui/toast/toast';
|
||||
import {message} from '../../../i18n/message';
|
||||
import {Role} from '../../../auth/role';
|
||||
import {showHttpErrorToast} from '../../../utils/http/show-http-error-toast';
|
||||
|
||||
interface Response extends BackendResponse {}
|
||||
|
||||
interface Payload {
|
||||
userIds: number[];
|
||||
}
|
||||
|
||||
export function useRemoveUsersFromRole(role: Role) {
|
||||
return useMutation({
|
||||
mutationFn: ({userIds}: Payload) =>
|
||||
removeUsersFromRole({userIds, roleId: role.id}),
|
||||
onSuccess: (response, payload) => {
|
||||
toast(
|
||||
message('Removed [one 1 user|other :count users] from “{role}“', {
|
||||
values: {count: payload.userIds.length, role: role.name},
|
||||
}),
|
||||
);
|
||||
},
|
||||
onError: err => showHttpErrorToast(err),
|
||||
});
|
||||
}
|
||||
|
||||
function removeUsersFromRole({
|
||||
roleId,
|
||||
userIds,
|
||||
}: Payload & {roleId: number}): Promise<Response> {
|
||||
return apiClient
|
||||
.post(`roles/${roleId}/remove-users`, {userIds})
|
||||
.then(r => r.data);
|
||||
}
|
||||
23
common/resources/client/admin/roles/requests/use-role.ts
Executable file
23
common/resources/client/admin/roles/requests/use-role.ts
Executable file
@@ -0,0 +1,23 @@
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {BackendResponse} from '@common/http/backend-response/backend-response';
|
||||
import {apiClient} from '@common/http/query-client';
|
||||
import {Role} from '@common/auth/role';
|
||||
import {useParams} from 'react-router-dom';
|
||||
|
||||
const Endpoint = (id: number | string) => `roles/${id}`;
|
||||
|
||||
export interface FetchRoleResponse extends BackendResponse {
|
||||
role: Role;
|
||||
}
|
||||
|
||||
export function useRole() {
|
||||
const {roleId} = useParams();
|
||||
return useQuery({
|
||||
queryKey: [Endpoint(roleId!)],
|
||||
queryFn: () => fetchRole(roleId!),
|
||||
});
|
||||
}
|
||||
|
||||
function fetchRole(roleId: number | string): Promise<FetchRoleResponse> {
|
||||
return apiClient.get(Endpoint(roleId)).then(response => response.data);
|
||||
}
|
||||
39
common/resources/client/admin/roles/requests/use-update-role.ts
Executable file
39
common/resources/client/admin/roles/requests/use-update-role.ts
Executable file
@@ -0,0 +1,39 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {apiClient, queryClient} from '../../../http/query-client';
|
||||
import {BackendResponse} from '../../../http/backend-response/backend-response';
|
||||
import {toast} from '../../../ui/toast/toast';
|
||||
import {Role} from '../../../auth/role';
|
||||
import {useTrans} from '../../../i18n/use-trans';
|
||||
import {message} from '../../../i18n/message';
|
||||
import {DatatableDataQueryKey} from '../../../datatable/requests/paginated-resources';
|
||||
import {showHttpErrorToast} from '../../../utils/http/show-http-error-toast';
|
||||
import {useNavigate} from '../../../utils/hooks/use-navigate';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
role: Role;
|
||||
}
|
||||
|
||||
interface Payload extends Partial<Role> {
|
||||
id: number;
|
||||
}
|
||||
|
||||
const Endpoint = (id: number) => `roles/${id}`;
|
||||
|
||||
export function useUpdateRole() {
|
||||
const {trans} = useTrans();
|
||||
const navigate = useNavigate();
|
||||
return useMutation({
|
||||
mutationFn: (payload: Payload) => updateRole(payload),
|
||||
onSuccess: response => {
|
||||
toast(trans(message('Role updated')));
|
||||
queryClient.invalidateQueries({queryKey: [Endpoint(response.role.id)]});
|
||||
queryClient.invalidateQueries({queryKey: DatatableDataQueryKey('roles')});
|
||||
navigate('/admin/roles');
|
||||
},
|
||||
onError: err => showHttpErrorToast(err),
|
||||
});
|
||||
}
|
||||
|
||||
function updateRole({id, ...payload}: Payload): Promise<Response> {
|
||||
return apiClient.put(Endpoint(id), payload).then(r => r.data);
|
||||
}
|
||||
34
common/resources/client/admin/roles/requests/user-create-role.ts
Executable file
34
common/resources/client/admin/roles/requests/user-create-role.ts
Executable file
@@ -0,0 +1,34 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {apiClient, queryClient} from '../../../http/query-client';
|
||||
import {BackendResponse} from '../../../http/backend-response/backend-response';
|
||||
import {toast} from '../../../ui/toast/toast';
|
||||
import {Role} from '../../../auth/role';
|
||||
import {useTrans} from '../../../i18n/use-trans';
|
||||
import {message} from '../../../i18n/message';
|
||||
import {DatatableDataQueryKey} from '../../../datatable/requests/paginated-resources';
|
||||
import {onFormQueryError} from '../../../errors/on-form-query-error';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
role: Role;
|
||||
}
|
||||
|
||||
export interface CreateRolePayload extends Partial<Role> {}
|
||||
|
||||
const Endpoint = 'roles';
|
||||
|
||||
export function useCreateRole(form: UseFormReturn<CreateRolePayload>) {
|
||||
const {trans} = useTrans();
|
||||
return useMutation({
|
||||
mutationFn: (payload: CreateRolePayload) => createRole(payload),
|
||||
onSuccess: () => {
|
||||
toast(trans(message('Created new role')));
|
||||
queryClient.invalidateQueries({queryKey: DatatableDataQueryKey('roles')});
|
||||
},
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
|
||||
function createRole({id, ...payload}: CreateRolePayload): Promise<Response> {
|
||||
return apiClient.post(Endpoint, payload).then(r => r.data);
|
||||
}
|
||||
41
common/resources/client/admin/roles/role-index-page-filters.ts
Executable file
41
common/resources/client/admin/roles/role-index-page-filters.ts
Executable file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
BackendFilter,
|
||||
FilterControlType,
|
||||
FilterOperator,
|
||||
} from '../../datatable/filters/backend-filter';
|
||||
import {message} from '../../i18n/message';
|
||||
import {
|
||||
createdAtFilter,
|
||||
updatedAtFilter,
|
||||
} from '@common/datatable/filters/timestamp-filters';
|
||||
|
||||
export const RoleIndexPageFilters: BackendFilter[] = [
|
||||
{
|
||||
key: 'type',
|
||||
label: message('Type'),
|
||||
description: message('Type of the role'),
|
||||
defaultOperator: FilterOperator.ne,
|
||||
control: {
|
||||
type: FilterControlType.Select,
|
||||
defaultValue: '01',
|
||||
options: [
|
||||
{
|
||||
key: '01',
|
||||
label: message('Sitewide'),
|
||||
value: 'sitewide',
|
||||
},
|
||||
{
|
||||
key: '02',
|
||||
label: message('Workspace'),
|
||||
value: 'workspace',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
createdAtFilter({
|
||||
description: message('Date role was created'),
|
||||
}),
|
||||
updatedAtFilter({
|
||||
description: message('Date role was last updated'),
|
||||
}),
|
||||
];
|
||||
96
common/resources/client/admin/roles/roles-index-page.tsx
Executable file
96
common/resources/client/admin/roles/roles-index-page.tsx
Executable file
@@ -0,0 +1,96 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import {DataTablePage} from '../../datatable/page/data-table-page';
|
||||
import {IconButton} from '../../ui/buttons/icon-button';
|
||||
import {EditIcon} from '../../icons/material/Edit';
|
||||
import {FormattedDate} from '../../i18n/formatted-date';
|
||||
import {ColumnConfig} from '../../datatable/column-config';
|
||||
import {Trans} from '../../i18n/trans';
|
||||
import {Role} from '../../auth/role';
|
||||
import teamSvg from './team.svg';
|
||||
import {DeleteSelectedItemsAction} from '../../datatable/page/delete-selected-items-action';
|
||||
import {DataTableEmptyStateMessage} from '../../datatable/page/data-table-emty-state-message';
|
||||
import {RoleIndexPageFilters} from './role-index-page-filters';
|
||||
import {DataTableExportCsvButton} from '../../datatable/csv-export/data-table-export-csv-button';
|
||||
import {DataTableAddItemButton} from '../../datatable/data-table-add-item-button';
|
||||
|
||||
const columnConfig: ColumnConfig<Role>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
allowsSorting: true,
|
||||
visibleInMode: 'all',
|
||||
header: () => <Trans message="Role" />,
|
||||
body: role => (
|
||||
<div>
|
||||
<div>
|
||||
<Trans message={role.name} />
|
||||
</div>
|
||||
<div className="text-muted text-xs overflow-x-hidden overflow-ellipsis">
|
||||
{role.description ? <Trans message={role.description} /> : undefined}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
maxWidth: 'max-w-100',
|
||||
allowsSorting: true,
|
||||
header: () => <Trans message="Type" />,
|
||||
body: role => <Trans message={role.type} />,
|
||||
},
|
||||
{
|
||||
key: 'updated_at',
|
||||
maxWidth: 'max-w-100',
|
||||
allowsSorting: true,
|
||||
header: () => <Trans message="Last updated" />,
|
||||
body: role => <FormattedDate date={role.updated_at} />,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
header: () => <Trans message="Actions" />,
|
||||
hideHeader: true,
|
||||
visibleInMode: 'all',
|
||||
align: 'end',
|
||||
width: 'w-42 flex-shrink-0',
|
||||
body: role => {
|
||||
return (
|
||||
<Link to={`${role.id}/edit`}>
|
||||
<IconButton size="md" className="text-muted">
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function RolesIndexPage() {
|
||||
return (
|
||||
<DataTablePage
|
||||
endpoint="roles"
|
||||
title={<Trans message="Roles" />}
|
||||
columns={columnConfig}
|
||||
filters={RoleIndexPageFilters}
|
||||
actions={<Actions />}
|
||||
selectedActions={<DeleteSelectedItemsAction />}
|
||||
emptyStateMessage={
|
||||
<DataTableEmptyStateMessage
|
||||
image={teamSvg}
|
||||
title={<Trans message="No roles have been created yet" />}
|
||||
filteringTitle={<Trans message="No matching roles" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Actions() {
|
||||
return (
|
||||
<Fragment>
|
||||
<DataTableExportCsvButton endpoint="roles/csv/export" />
|
||||
<DataTableAddItemButton elementType={Link} to="new">
|
||||
<Trans message="Add new role" />
|
||||
</DataTableAddItemButton>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
2
common/resources/client/admin/roles/team.svg
Executable file
2
common/resources/client/admin/roles/team.svg
Executable file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
Reference in New Issue
Block a user