From 8f7044207d1facdbf8dc2a668f27418720bde2ae Mon Sep 17 00:00:00 2001 From: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:40:25 +0800 Subject: [PATCH] On Company Show, in team section, I can detach a person from a company (#1202) * On Company Show, in team section, I can detach a person from a company Co-authored-by: v1b3m Co-authored-by: RubensRafael * On Company Show, in team section, I can detach a person from a company Co-authored-by: v1b3m Co-authored-by: RubensRafael * Temporary fix disconnect optional relations Co-authored-by: v1b3m Co-authored-by: RubensRafael * Refactor the PR logic Co-authored-by: v1b3m Co-authored-by: RubensRafael * Add requested changes Co-authored-by: v1b3m Co-authored-by: RubensRafael * Refactor the dropdown Co-authored-by: v1b3m Co-authored-by: RubensRafael --------- Co-authored-by: v1b3m Co-authored-by: RubensRafael --- .../modules/people/components/PeopleCard.tsx | 98 ++++++++++++++++++- server/src/ability/ability.util.ts | 22 +++-- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/front/src/modules/people/components/PeopleCard.tsx b/front/src/modules/people/components/PeopleCard.tsx index 94226d3366..236100241c 100644 --- a/front/src/modules/people/components/PeopleCard.tsx +++ b/front/src/modules/people/components/PeopleCard.tsx @@ -1,17 +1,34 @@ +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { getOperationName } from '@apollo/client/utilities'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; +import { IconDotsVertical, IconLinkOff } from '@tabler/icons-react'; +import { IconButton } from '@/ui/button/components/IconButton'; +import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; +import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { Avatar } from '@/users/components/Avatar'; -import { Person } from '~/generated/graphql'; +import { Person, useUpdateOnePersonMutation } from '~/generated/graphql'; + +import { GET_PEOPLE } from '../graphql/queries/getPeople'; export type PeopleCardProps = { person: Pick; hasBottomBorder?: boolean; }; -const StyledCard = styled.div<{ hasBottomBorder: boolean }>` +const StyledCard = styled.div<{ + isHovered: boolean; + hasBottomBorder?: boolean; +}>` align-items: center; align-self: stretch; + background: ${({ theme, isHovered }) => + isHovered ? theme.background.tertiary : 'auto'}; border-bottom: 1px solid ${({ theme, hasBottomBorder }) => hasBottomBorder ? theme.border.color.light : 'transparent'}; @@ -19,7 +36,6 @@ const StyledCard = styled.div<{ hasBottomBorder: boolean }>` gap: ${({ theme }) => theme.spacing(2)}; height: ${({ theme }) => theme.spacing(8)}; padding: ${({ theme }) => theme.spacing(3)}; - &:hover { background: ${({ theme }) => theme.background.tertiary}; cursor: pointer; @@ -57,8 +73,64 @@ export function PeopleCard({ hasBottomBorder = true, }: PeopleCardProps) { const navigate = useNavigate(); + const [isHovered, setIsHovered] = useState(false); + const [isOptionsOpen, setIsOptionsOpen] = useState(false); + const [updatePerson] = useUpdateOnePersonMutation(); + const { refs, floatingStyles } = useFloating({ + strategy: 'absolute', + middleware: [offset(10), flip()], + whileElementsMounted: autoUpdate, + placement: 'right-start', + }); + + const theme = useTheme(); + + useListenClickOutside({ + refs: [refs.floating], + callback: () => { + setIsOptionsOpen(false); + if (isOptionsOpen) { + setIsHovered(false); + } + }, + }); + + function handleMouseEnter() { + setIsHovered(true); + } + + function handleMouseLeave() { + if (!isOptionsOpen) { + setIsHovered(false); + } + } + + function handleToggleOptions(e: React.MouseEvent) { + e.stopPropagation(); + setIsOptionsOpen(!isOptionsOpen); + } + + function handleDetachPerson() { + updatePerson({ + variables: { + where: { + id: person.id, + }, + data: { + company: { + disconnect: true, + }, + }, + }, + refetchQueries: [getOperationName(GET_PEOPLE) ?? ''], + }); + } + return ( navigate(`/person/${person.id}`)} hasBottomBorder={hasBottomBorder} > @@ -72,6 +144,26 @@ export function PeopleCard({ {person.displayName} {person.jobTitle && {person.jobTitle}} + {isHovered && ( +
+ } + /> + {isOptionsOpen && ( + + e.stopPropagation()}> + + } size="small" /> + Detach relation + + + + )} +
+ )}
); } diff --git a/server/src/ability/ability.util.ts b/server/src/ability/ability.util.ts index 94bb193b7e..bba6c16810 100644 --- a/server/src/ability/ability.util.ts +++ b/server/src/ability/ability.util.ts @@ -59,6 +59,19 @@ const simpleAbilityCheck: OperationAbilityChecker = async ( prisma, data, ) => { + // TODO: Replace user by workspaceMember and remove this check + if ( + modelName === 'User' || + modelName === 'UserSettings' || + modelName === 'Workspace' + ) { + return true; + } + + if (typeof data === 'boolean') { + return true; + } + // Extract entity name from model name const entity = camelCase(modelName); // Handle all operations cases @@ -76,15 +89,6 @@ const simpleAbilityCheck: OperationAbilityChecker = async ( // Check if user try to connect an element that is not allowed to read for (const item of items) { - // TODO: Replace user by workspaceMember and remove this check - if ( - modelName === 'User' || - modelName === 'UserSettings' || - modelName === 'Workspace' - ) { - return true; - } - if (!ability.can(AbilityAction.Read, subject(modelName, item))) { return false; }