Skip to content

Commit

Permalink
[DOP-20030] create and update group
Browse files Browse the repository at this point in the history
  • Loading branch information
Zablisya committed Oct 14, 2024
1 parent e9ed451 commit 7c1f9a6
Show file tree
Hide file tree
Showing 57 changed files with 508 additions and 131 deletions.
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AntdConfigProvider, AppRoutes } from '@app/config';
import { SelectedGroupProvider } from '@entities/group';
import { TanstackQueryProvider } from '@shared/config';
import React from 'react';

export const App = () => (
<TanstackQueryProvider>
<AntdConfigProvider>
<AppRoutes />
<SelectedGroupProvider>
<AppRoutes />
</SelectedGroupProvider>
</AntdConfigProvider>
</TanstackQueryProvider>
);
15 changes: 10 additions & 5 deletions src/app/config/router/instance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { createBrowserRouter } from 'react-router-dom';
import { UserDetailPage, UserListPage } from '@pages/user';
import { LoginPage } from '@pages/auth';
import { AuthLayout, ErrorLayout, PrivateLayout } from '@app/layouts';
import { GroupDetailPage, GroupListPage } from '@pages/groups';
import { CreateGroupPage, GroupDetailPage, GroupListPage, UpdateGroupPage } from '@pages/groups';
import { AuthProvider } from '@entities/auth';
import { SelectedGroupProvider } from '@entities/group';

import { ErrorBoundary, NotFoundError } from '../errorBoundary';

Expand All @@ -32,9 +31,7 @@ export const router = createBrowserRouter([
element: (
<PrivateRoute>
<AuthProvider>
<SelectedGroupProvider>
<PrivateLayout />
</SelectedGroupProvider>
<PrivateLayout />
</AuthProvider>
</PrivateRoute>
),
Expand All @@ -55,6 +52,14 @@ export const router = createBrowserRouter([
path: '/groups/:id',
element: <GroupDetailPage />,
},
{
path: '/groups/create',
element: <CreateGroupPage />,
},
{
path: '/groups/:id/update',
element: <UpdateGroupPage />,
},
],
},
{
Expand Down
4 changes: 0 additions & 4 deletions src/app/styles/antd.less
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
border-radius: 0;
}

.ant-descriptions-view {
max-width: 1000px;
}

.ant-descriptions-item-label {
width: 300px;
font-weight: 700;
Expand Down
19 changes: 17 additions & 2 deletions src/entities/group/api/groupService.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { axiosInstance } from '@shared/config';
import { PaginationResponse } from '@shared/types';

import { GetGroupRequest, GetGroupsRequest, Group } from './types';
import {
CreateGroupRequest,
GetGroupRequest,
GetGroupsRequest,
Group,
GroupFromList,
UpdateGroupRequest,
} from './types';

export const groupService = {
getGroups: (params: GetGroupsRequest): Promise<PaginationResponse<Group>> => {
getGroups: (params: GetGroupsRequest): Promise<PaginationResponse<GroupFromList>> => {
return axiosInstance.get('groups', { params });
},

getGroup: ({ id }: GetGroupRequest): Promise<Group> => {
return axiosInstance.get(`groups/${id}`);
},

createGroup: (data: CreateGroupRequest): Promise<Group> => {
return axiosInstance.post(`groups`, data);
},

updateGroup: ({ id, ...data }: UpdateGroupRequest): Promise<Group> => {
return axiosInstance.patch(`groups/${id}`, data);
},
};
19 changes: 18 additions & 1 deletion src/entities/group/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PaginationRequest } from '@shared/types';
import { PaginationRequest, UserRole } from '@shared/types';

export interface Group {
id: number;
Expand All @@ -7,9 +7,26 @@ export interface Group {
owner_id: number;
}

export interface GroupFromList {
data: Group;
role: keyof typeof UserRole;
}

//TODO: This interface will be extended
export interface GetGroupsRequest extends PaginationRequest {}

export interface GetGroupRequest {
id: number | string;
}

export interface CreateGroupRequest {
name: string;
description: string;
}

export interface UpdateGroupRequest {
id: number | string;
name: string;
description: string;
owner_id: number;
}
1 change: 0 additions & 1 deletion src/entities/group/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './api';
export * from './ui';
export * from './providers';
export * from './hooks';
4 changes: 2 additions & 2 deletions src/entities/group/providers/SelectedGroupProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { memo, PropsWithChildren, useState } from 'react';

import { Group } from '../../api';
import { GroupFromList } from '../../api';
import { SelectedGroupContext } from '../../constants';

export const SelectedGroupProvider = memo(({ children }: PropsWithChildren) => {
const [selectedGroup, setSelectedGroup] = useState<Group | null>(null);
const [selectedGroup, setSelectedGroup] = useState<GroupFromList | null>(null);

const contextValue = {
group: selectedGroup,
Expand Down
6 changes: 3 additions & 3 deletions src/entities/group/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Group } from './api';
import { GroupFromList } from './api';

export interface SelectedGroupContextProps {
group: Group | null;
selectGroup: (group: Group) => void;
group: GroupFromList | null;
selectGroup: (group: GroupFromList) => void;
}
1 change: 0 additions & 1 deletion src/entities/group/ui/index.ts

This file was deleted.

5 changes: 1 addition & 4 deletions src/entities/user/ui/UserDetailInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React, { memo } from 'react';
import { Descriptions } from 'antd';
import { User } from '@entities/user';

interface UserDetailInfoProps {
user: User;
}
import { UserDetailInfoProps } from './types';

export const UserDetailInfo = memo(({ user }: UserDetailInfoProps) => {
return (
Expand Down
5 changes: 5 additions & 0 deletions src/entities/user/ui/UserDetailInfo/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { User } from '@entities/user';

export interface UserDetailInfoProps {
user: User;
}
35 changes: 35 additions & 0 deletions src/features/group/CreateGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { memo } from 'react';
import { FormButtons, ManagedForm } from '@shared/ui';
import { Group, GroupQueryKey, groupService } from '@entities/group';
import { Form, Input } from 'antd';
import { useNavigate } from 'react-router-dom';

export const CreateGroup = memo(() => {
const navigate = useNavigate();

const onSuccess = (response: Group) => {
navigate(`/groups/${response.id}`);
};

const onCancel = () => {
navigate('/groups');
};

return (
<ManagedForm
mutationFunction={groupService.createGroup}
onSuccess={onSuccess}
keysInvalidateQueries={[[{ queryKey: [GroupQueryKey.GET_GROUPS] }]]}
>
<Form.Item label="Name" name="name" rules={[{ required: true }]}>
<Input size="large" />
</Form.Item>

<Form.Item label="Description" name="description">
<Input size="large" />
</Form.Item>

<FormButtons onCancel={onCancel} />
</ManagedForm>
);
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import React, { memo } from 'react';
import { Descriptions } from 'antd';
import { Group } from '@entities/group/api';
import { User } from '@entities/user';
import { Link } from 'react-router-dom';

interface GroupDetailInfoProps {
group: Group;
owner: User;
}
import { GroupDetailInfoProps } from './types';

export const GroupDetailInfo = memo(({ group, owner }: GroupDetailInfoProps) => {
export const GroupDetailInfo = memo(({ group, owner, ...props }: GroupDetailInfoProps) => {
return (
<Descriptions title="Group info" bordered>
<Descriptions title="Group info" bordered {...props}>
<Descriptions.Item label="Id" span={3}>
{group.id}
</Descriptions.Item>
Expand Down
8 changes: 8 additions & 0 deletions src/features/group/GroupDetailInfo/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Group } from '@entities/group';
import { User } from '@entities/user';
import { DescriptionsProps } from 'antd';

export interface GroupDetailInfoProps extends DescriptionsProps {
group: Group;
owner: User;
}
8 changes: 5 additions & 3 deletions src/features/group/GroupList/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import React from 'react';
import { PaginationResponse } from '@shared/types';
import { TableColumns } from '@shared/ui';
import { Link } from 'react-router-dom';
import { Group } from '@entities/group';
import { GroupFromList } from '@entities/group';

export const GROUP_LIST_COLUMNS: TableColumns<PaginationResponse<Group>> = [
export const GROUP_LIST_COLUMNS: TableColumns<PaginationResponse<GroupFromList>> = [
{
title: 'Id',
dataIndex: 'id',
render: (name, record) => record.data.id,
width: 250,
},
{
title: 'Name',
dataIndex: 'name',
render: (name, record) => <Link to={`/groups/${record.id}`}>{name}</Link>,
render: (name, record) => <Link to={`/groups/${record.data.id}`}>{record.data.name}</Link>,
},
{
title: 'Description',
dataIndex: 'description',
render: (name, record) => record.data.description,
},
];
15 changes: 13 additions & 2 deletions src/features/group/GroupList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import React, { memo } from 'react';
import { ManagedTable } from '@shared/ui';
import { GroupQueryKey, groupService } from '@entities/group';
import { GroupFromList, GroupQueryKey, groupService } from '@entities/group';
import { useNavigate } from 'react-router-dom';
import { hasAccessByUserRole } from '@shared/utils';
import { UserRole } from '@shared/types';

import { GROUP_LIST_COLUMNS } from './constants';

export const GroupList = memo(() => {
const navigate = useNavigate();

const handleUpdateRowClick = (record: GroupFromList) => {
navigate(`/groups/${record.data.id}/update`);
};

return (
<ManagedTable
queryKey={[GroupQueryKey.GET_GROUPS]}
queryFunction={groupService.getGroups}
columns={GROUP_LIST_COLUMNS}
rowKey="id"
isRenderUpdateRowAction={(record) => hasAccessByUserRole(UserRole.Owner, record.role)}
onUpdateRowClick={handleUpdateRowClick}
rowKey={(row) => row.data.id}
/>
);
});
11 changes: 6 additions & 5 deletions src/features/group/SelectGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import React, { memo } from 'react';
import { Group, GroupQueryKey, groupService, useSelectedGroup } from '@entities/group';
import { GroupFromList, GroupQueryKey, groupService, useSelectedGroup } from '@entities/group';
import { ManagedSelect, OptionItem } from '@shared/ui';

export const SelectGroup = memo(() => {
const { selectGroup } = useSelectedGroup();
const { group: selectedGroup, selectGroup } = useSelectedGroup();

const handleSelectGroup = (value: string, option: OptionItem<Group>) => {
const handleSelectGroup = (value: number, option: OptionItem<GroupFromList>) => {
selectGroup(option.data);
};

return (
<ManagedSelect
queryKey={[GroupQueryKey.GET_GROUPS]}
queryFunction={groupService.getGroups}
value={selectedGroup?.data.id}
onSelect={handleSelectGroup}
optionValue="id"
optionLabel="name"
renderOptionLabel={(group) => group.data.name}
renderOptionValue={(group) => group.data.id}
placeholder="Select group"
/>
);
Expand Down
63 changes: 63 additions & 0 deletions src/features/group/UpdateGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { memo, useMemo } from 'react';
import { FormButtons, ManagedForm, ManagedSelect } from '@shared/ui';
import { Group, GroupQueryKey, groupService } from '@entities/group';
import { Form, Input } from 'antd';
import { useNavigate } from 'react-router-dom';
import { UserQueryKey, userService } from '@entities/user';

import { UpdateGroupForm, UpdateGroupProps } from './types';
import { getUpdateGroupInitialValues } from './utils';

export const UpdateGroup = memo(({ group }: UpdateGroupProps) => {
const navigate = useNavigate();

const initialValues = useMemo(() => {
return getUpdateGroupInitialValues(group);
}, [group]);

const handleUpdateGroup = (values: UpdateGroupForm) => {
return groupService.updateGroup({ ...values, id: group.id });
};

const onSuccess = (response: Group) => {
navigate(`/groups/${response.id}`);
};

const onCancel = () => {
navigate('/groups');
};

return (
<ManagedForm<UpdateGroupForm, Group>
mutationFunction={handleUpdateGroup}
initialValues={initialValues}
onSuccess={onSuccess}
keysInvalidateQueries={[
[{ queryKey: [GroupQueryKey.GET_GROUPS] }],
[{ queryKey: [GroupQueryKey.GET_GROUP, group.id] }],
]}
>
<Form.Item label="Name" name="name" rules={[{ required: true }]}>
<Input size="large" />
</Form.Item>

<Form.Item label="Description" name="description">
<Input size="large" />
</Form.Item>

<Form.Item label="Owner" name="owner_id" rules={[{ required: true }]}>
<ManagedSelect
size="large"
queryKey={[UserQueryKey.GET_USERS]}
queryFunction={userService.getUsers}
renderOptionValue={(user) => user.id}
renderOptionLabel={(user) => user.username}
//TODO: [DOP-20026] Need to delete prop "disabled" when the backend leaves the user with access to the group, even after changing the owner
disabled
/>
</Form.Item>

<FormButtons onCancel={onCancel} />
</ManagedForm>
);
});
7 changes: 7 additions & 0 deletions src/features/group/UpdateGroup/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Group, UpdateGroupRequest } from '@entities/group';

export interface UpdateGroupForm extends Omit<UpdateGroupRequest, 'id'> {}

export interface UpdateGroupProps {
group: Group;
}
Loading

0 comments on commit 7c1f9a6

Please sign in to comment.