From e49f02379a01355acfbe4f4c196980272bbab4b5 Mon Sep 17 00:00:00 2001 From: Katrinputrina Date: Wed, 13 Nov 2024 18:35:49 +0300 Subject: [PATCH] feat(UserProfile): add curator in profile --- .../migration.sql | 5 +++ src/components/UserPage/UserPage.i18n/en.json | 1 - src/components/UserPage/UserPage.i18n/ru.json | 3 +- src/components/UserPage/UserPage.tsx | 9 ----- .../UserSummary/UserSummary.i18n/en.json | 3 +- .../UserSummary/UserSummary.i18n/ru.json | 3 +- src/components/UserSummary/UserSummary.tsx | 18 +++++++--- .../UserUpdateForm.i18n/en.json | 2 +- .../UserUpdateForm.i18n/ru.json | 2 +- .../UserUpdateForm/UserUpdateForm.module.css | 7 +++- .../UserUpdateForm/UserUpdateForm.tsx | 20 +++++++---- src/modules/externalUserTypes.ts | 2 +- src/modules/userMethods.ts | 34 ++++++++++++++----- src/modules/userSchemas.ts | 2 +- src/modules/userTypes.ts | 8 +++-- src/trpc/router/userRouter.ts | 14 ++++++-- 16 files changed, 88 insertions(+), 45 deletions(-) create mode 100644 prisma/migrations/20241115074630_add_curators_in_user_profile/migration.sql diff --git a/prisma/migrations/20241115074630_add_curators_in_user_profile/migration.sql b/prisma/migrations/20241115074630_add_curators_in_user_profile/migration.sql new file mode 100644 index 00000000..2d922861 --- /dev/null +++ b/prisma/migrations/20241115074630_add_curators_in_user_profile/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "_userCurators" DROP CONSTRAINT "_userCurators_B_fkey"; + +-- AddForeignKey +ALTER TABLE "_userCurators" ADD CONSTRAINT "_userCurators_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/components/UserPage/UserPage.i18n/en.json b/src/components/UserPage/UserPage.i18n/en.json index b0d5a9b5..8e78ed5a 100644 --- a/src/components/UserPage/UserPage.i18n/en.json +++ b/src/components/UserPage/UserPage.i18n/en.json @@ -12,6 +12,5 @@ "Transfer": "", "Supplemental: ": "", "Previous names": "", - "Curator": "", "Curator of users": "" } diff --git a/src/components/UserPage/UserPage.i18n/ru.json b/src/components/UserPage/UserPage.i18n/ru.json index f3a5f777..efa953e3 100644 --- a/src/components/UserPage/UserPage.i18n/ru.json +++ b/src/components/UserPage/UserPage.i18n/ru.json @@ -12,6 +12,5 @@ "Transfer": "Перевод", "Supplemental: ": "Дополнительно: ", "Previous names": "Предыдущие ФИО", - "Curator": "", - "Curator of users": "" + "Curator of users": "Куратор у пользователей" } diff --git a/src/components/UserPage/UserPage.tsx b/src/components/UserPage/UserPage.tsx index 57faa7d5..f08278c6 100644 --- a/src/components/UserPage/UserPage.tsx +++ b/src/components/UserPage/UserPage.tsx @@ -312,15 +312,6 @@ export const UserPageInner = ({ user }: UserPageInnerProps) => { ))} ))} - {nullable(user.curatorOf?.length, () => ( - - {user.curatorOf?.map((u) => ( - - - - ))} - - ))} {nullable(user.supervisorIn?.length, () => ( diff --git a/src/components/UserSummary/UserSummary.i18n/en.json b/src/components/UserSummary/UserSummary.i18n/en.json index 9351f2a4..f002822c 100644 --- a/src/components/UserSummary/UserSummary.i18n/en.json +++ b/src/components/UserSummary/UserSummary.i18n/en.json @@ -1,4 +1,5 @@ { "Quick summary": "", - "Supervisor": "" + "Supervisor": "", + "Curators:": "" } diff --git a/src/components/UserSummary/UserSummary.i18n/ru.json b/src/components/UserSummary/UserSummary.i18n/ru.json index 8d14a03b..9804db76 100644 --- a/src/components/UserSummary/UserSummary.i18n/ru.json +++ b/src/components/UserSummary/UserSummary.i18n/ru.json @@ -1,4 +1,5 @@ { "Quick summary": "Быстрая сводка", - "Supervisor": "Руководитель" + "Supervisor": "Руководитель", + "Curators:": "Кураторы" } diff --git a/src/components/UserSummary/UserSummary.tsx b/src/components/UserSummary/UserSummary.tsx index 59ddd058..9dd7a94c 100644 --- a/src/components/UserSummary/UserSummary.tsx +++ b/src/components/UserSummary/UserSummary.tsx @@ -3,14 +3,14 @@ import { gapS, gray9 } from '@taskany/colors'; import styled from 'styled-components'; import { User } from 'prisma/prisma-client'; -import { UserSupervisor } from '../../modules/userTypes'; +import { UserCurators, UserSupervisor } from '../../modules/userTypes'; import { NarrowSection } from '../NarrowSection'; import { UserListItem } from '../UserListItem/UserListItem'; import { tr } from './UserSummary.i18n'; interface UserSummaryProps { - user: User & UserSupervisor; + user: User & UserSupervisor & UserCurators; } const StyledSupervisorText = styled(Text)` @@ -21,13 +21,21 @@ const StyledSupervisorText = styled(Text)` export const UserSummary = ({ user }: UserSummaryProps) => { return ( <> - {nullable(user.supervisor, (supervisor) => ( - + + {nullable(user.supervisor, (supervisor) => ( {tr('Supervisor')} - + ))} + + {nullable(user.curators, (curators) => ( + + {tr('Curators:')} + {curators.map((curator) => ( + + ))} + ))} ); diff --git a/src/components/UserUpdateForm/UserUpdateForm.i18n/en.json b/src/components/UserUpdateForm/UserUpdateForm.i18n/en.json index a083a3c5..5184ef3b 100644 --- a/src/components/UserUpdateForm/UserUpdateForm.i18n/en.json +++ b/src/components/UserUpdateForm/UserUpdateForm.i18n/en.json @@ -7,6 +7,6 @@ "Organization": "", "Supplemental:": "", "Save previous name:": "", - "Curator:": "", + "Curators:": "", "Curator": "" } diff --git a/src/components/UserUpdateForm/UserUpdateForm.i18n/ru.json b/src/components/UserUpdateForm/UserUpdateForm.i18n/ru.json index 599f2753..6c5e632b 100644 --- a/src/components/UserUpdateForm/UserUpdateForm.i18n/ru.json +++ b/src/components/UserUpdateForm/UserUpdateForm.i18n/ru.json @@ -7,6 +7,6 @@ "Organization": "Организация", "Supplemental:": "Дополнительно", "Save previous name:": "Сохранить предыдущее ФИО", - "Curator:": "", + "Curators:": "Кураторы", "Curator": "" } diff --git a/src/components/UserUpdateForm/UserUpdateForm.module.css b/src/components/UserUpdateForm/UserUpdateForm.module.css index 00eb08ed..82c73984 100644 --- a/src/components/UserUpdateForm/UserUpdateForm.module.css +++ b/src/components/UserUpdateForm/UserUpdateForm.module.css @@ -19,4 +19,9 @@ display: flex; align-items: center; gap: var(--gap-s); -} \ No newline at end of file +} + +.Curator { + display: flex; + align-items: center; +} diff --git a/src/components/UserUpdateForm/UserUpdateForm.tsx b/src/components/UserUpdateForm/UserUpdateForm.tsx index e795c0e2..ddb6209d 100644 --- a/src/components/UserUpdateForm/UserUpdateForm.tsx +++ b/src/components/UserUpdateForm/UserUpdateForm.tsx @@ -12,11 +12,12 @@ import { Text, nullable, } from '@taskany/bricks'; -import { Checkbox, FormControl } from '@taskany/bricks/harmony'; +import { Checkbox } from '@taskany/bricks/harmony'; import { zodResolver } from '@hookform/resolvers/zod'; import { User } from 'prisma/prisma-client'; import { gray8 } from '@taskany/colors'; +import { FormControl } from '../FormControl/FormControl'; import { UserComboBox } from '../UserComboBox/UserComboBox'; import { EditUser, EditUserFields, editUserFieldsSchema } from '../../modules/userSchemas'; import { useUserMutations } from '../../modules/userHooks'; @@ -61,12 +62,12 @@ export const UserUpdateForm = ({ onClose, user }: UserDataFormProps) => { name: user.name || undefined, savePreviousName: undefined, organizationUnitId: user.organizationUnitId || undefined, - curatorIds: user.curators?.map(({ id }) => id), + curatorIds: user?.curators?.map(({ id }) => id) || [], }, mode: 'onChange', resolver: zodResolver(editUserFieldsSchema), }); - + const curatorIds = watch('curatorIds'); const savePreviousName = watch('savePreviousName') ?? false; const updateUser = async (data: EditUser) => { @@ -74,13 +75,14 @@ export const UserUpdateForm = ({ onClose, user }: UserDataFormProps) => { onClose(); }; - const onUsersChange = (users: User[], type: keyof UserFormExternalTeamBlockType) => { + const onCuratorsChange = (users: User[], type: keyof UserFormExternalTeamBlockType) => { setValue( type, users.map((user) => user.id), ); trigger(type); }; + return ( <> @@ -118,12 +120,16 @@ export const UserUpdateForm = ({ onClose, user }: UserDataFormProps) => { }} /> +
- + + {tr('Curators:')} + + onUsersChange(users, 'curatorIds')} + selectedUsers={curatorIds ?? undefined} + onChange={(curators) => onCuratorsChange(curators, 'curatorIds')} error={errors.curatorIds} /> diff --git a/src/modules/externalUserTypes.ts b/src/modules/externalUserTypes.ts index 591ed267..9e2c289e 100644 --- a/src/modules/externalUserTypes.ts +++ b/src/modules/externalUserTypes.ts @@ -2,7 +2,7 @@ export interface ExternalUserUpdate { email: string; name?: string; supervisorId?: string | null; - curatorIds?: string[] | null; + curators?: string[] | null; active?: boolean; login?: string; } diff --git a/src/modules/userMethods.ts b/src/modules/userMethods.ts index bb295bfc..b8ce8d91 100644 --- a/src/modules/userMethods.ts +++ b/src/modules/userMethods.ts @@ -305,12 +305,10 @@ export const userMethods = { }); if (!user) throw new TRPCError({ code: 'NOT_FOUND', message: `No user with id ${id}` }); - const { curators } = user; const showAchievements = user.settings ? user.settings.showAchievements : true; const userWithGroupMeta = { ...user, - curatorIds: curators.map(({ id }) => id), achievements: showAchievements ? user.achievements : undefined, memberships: await Promise.all( user.memberships.map(async (m) => ({ @@ -405,20 +403,40 @@ export const userMethods = { return prisma.user.findMany({ where: { memberships: { some: { groupId } }, active: { not: true } } }); }, - edit: async ({ id, savePreviousName, supplementalPosition, ...data }: EditUserFields) => { - if (data.organizationUnitId) { + edit: async ({ + id, + savePreviousName, + supplementalPosition, + organizationUnitId, + curatorIds, + ...data + }: EditUserFields) => { + const updateUser: Prisma.UserUpdateInput = data; + + if (organizationUnitId) { const newOrganization = await prisma.organizationUnit.findUnique({ where: { - id: data.organizationUnitId, + id: organizationUnitId, }, }); if (!newOrganization) { throw new TRPCError({ code: 'BAD_REQUEST', - message: `No organization with id ${data.organizationUnitId}`, + message: `No organization with id ${organizationUnitId}`, }); } + + updateUser.organizationUnit = { connect: { id: organizationUnitId } }; + } + + if (curatorIds) { + updateUser.curators = { + connect: curatorIds.map((id) => ({ id })), + }; + if (curatorIds.includes(id)) { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'You can`t add the current user to the curators' }); + } } if (supplementalPosition) { @@ -441,7 +459,6 @@ export const userMethods = { await externalUserMethods.update(id, { name: data.name, supervisorId: data.supervisorId, - curatorIds: data.curatorIds, }); if (savePreviousName) { @@ -450,8 +467,7 @@ export const userMethods = { await prisma.userNames.create({ data: { userId: id, name: userBeforeUpdate.name } }); } } - - return prisma.user.update({ where: { id }, data }); + return prisma.user.update({ where: { id }, data: updateUser, include: { curators: true } }); }, editActiveState: async (data: EditUserActiveState): Promise => { diff --git a/src/modules/userSchemas.ts b/src/modules/userSchemas.ts index b5f0203a..9ebf52e8 100644 --- a/src/modules/userSchemas.ts +++ b/src/modules/userSchemas.ts @@ -77,7 +77,7 @@ export const editUserSchema = z.object({ name: z.string().optional(), savePreviousName: z.boolean().optional(), supervisorId: z.string().nullish(), - curatorIds: z.array(z.string()).nullish(), + curatorIds: z.array(z.string()).optional(), supplementalPosition: z .object({ organizationUnitId: z.string(), diff --git a/src/modules/userTypes.ts b/src/modules/userTypes.ts index 136aac5f..e6198d45 100644 --- a/src/modules/userTypes.ts +++ b/src/modules/userTypes.ts @@ -50,13 +50,15 @@ export interface UserMemberships { export interface UserSupervisor { supervisor: Nullish; } -export interface UserCurators { - curators: Nullish; -} export interface UserSupervisorOf { supervisorOf: Nullish; } + +export interface UserCurators { + curators: Nullish; +} + export interface UserCuratorOf { curatorOf: Nullish; } diff --git a/src/trpc/router/userRouter.ts b/src/trpc/router/userRouter.ts index 7b10a8d1..cc052cbf 100644 --- a/src/trpc/router/userRouter.ts +++ b/src/trpc/router/userRouter.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TRPCError } from '@trpc/server'; import { accessCheck, checkRoleForAccess } from '../../utils/access'; import { protectedProcedure, router } from '../trpcBackend'; @@ -141,8 +142,16 @@ export const userRouter = router({ edit: protectedProcedure.input(editUserFieldsSchema).mutation(async ({ input, ctx }) => { accessCheck(checkRoleForAccess(ctx.session.user.role, 'editUser')); - const userBefore = await userMethods.getByIdOrThrow(input.id); + + const userBefore = await prisma.user.findUnique({ where: { id: input.id }, include: { curators: true } }); + if (!userBefore) throw new TRPCError({ code: 'NOT_FOUND', message: `No user with id ${input.id}` }); + const result = await userMethods.edit(input); + const resultWithCurator = await prisma.user.update({ + where: { id: input.id }, + data: input, + include: { curators: true }, + }); const { before, after } = dropUnchangedValuesFromEvent( { @@ -151,13 +160,14 @@ export const userRouter = router({ organizationalUnitId: userBefore.organizationUnitId ?? undefined, email: userBefore.email, savePreviousName: input.savePreviousName, - // curators: userBefore.curators + curatorIds: userBefore.curators.map(({ id }) => id), }, { name: result.name, supervisorId: result.supervisorId, organizationalUnitId: result.organizationUnitId ?? undefined, email: result.email, + curatorIds: resultWithCurator.curators.map(({ id }) => id), }, ); await historyEventMethods.create({ user: ctx.session.user.id }, 'editUser', {