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 <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>

* On Company Show, in team section, I can detach a person from a company

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>

* Temporary fix disconnect optional relations

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>

* Refactor the PR logic

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>

* Refactor the dropdown

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>

---------

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>
This commit is contained in:
gitstart-twenty 2023-08-15 23:40:25 +08:00 committed by GitHub
parent 9bbdf933e9
commit 8f7044207d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 12 deletions

View File

@ -1,17 +1,34 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; 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 { 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 = { export type PeopleCardProps = {
person: Pick<Person, 'id' | 'avatarUrl' | 'displayName' | 'jobTitle'>; person: Pick<Person, 'id' | 'avatarUrl' | 'displayName' | 'jobTitle'>;
hasBottomBorder?: boolean; hasBottomBorder?: boolean;
}; };
const StyledCard = styled.div<{ hasBottomBorder: boolean }>` const StyledCard = styled.div<{
isHovered: boolean;
hasBottomBorder?: boolean;
}>`
align-items: center; align-items: center;
align-self: stretch; align-self: stretch;
background: ${({ theme, isHovered }) =>
isHovered ? theme.background.tertiary : 'auto'};
border-bottom: 1px solid border-bottom: 1px solid
${({ theme, hasBottomBorder }) => ${({ theme, hasBottomBorder }) =>
hasBottomBorder ? theme.border.color.light : 'transparent'}; hasBottomBorder ? theme.border.color.light : 'transparent'};
@ -19,7 +36,6 @@ const StyledCard = styled.div<{ hasBottomBorder: boolean }>`
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(8)}; height: ${({ theme }) => theme.spacing(8)};
padding: ${({ theme }) => theme.spacing(3)}; padding: ${({ theme }) => theme.spacing(3)};
&:hover { &:hover {
background: ${({ theme }) => theme.background.tertiary}; background: ${({ theme }) => theme.background.tertiary};
cursor: pointer; cursor: pointer;
@ -57,8 +73,64 @@ export function PeopleCard({
hasBottomBorder = true, hasBottomBorder = true,
}: PeopleCardProps) { }: PeopleCardProps) {
const navigate = useNavigate(); 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<HTMLButtonElement>) {
e.stopPropagation();
setIsOptionsOpen(!isOptionsOpen);
}
function handleDetachPerson() {
updatePerson({
variables: {
where: {
id: person.id,
},
data: {
company: {
disconnect: true,
},
},
},
refetchQueries: [getOperationName(GET_PEOPLE) ?? ''],
});
}
return ( return (
<StyledCard <StyledCard
isHovered={isHovered}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={() => navigate(`/person/${person.id}`)} onClick={() => navigate(`/person/${person.id}`)}
hasBottomBorder={hasBottomBorder} hasBottomBorder={hasBottomBorder}
> >
@ -72,6 +144,26 @@ export function PeopleCard({
<StyledTitle>{person.displayName}</StyledTitle> <StyledTitle>{person.displayName}</StyledTitle>
{person.jobTitle && <StyledJobTitle>{person.jobTitle}</StyledJobTitle>} {person.jobTitle && <StyledJobTitle>{person.jobTitle}</StyledJobTitle>}
</StyledCardInfo> </StyledCardInfo>
{isHovered && (
<div ref={refs.setReference}>
<IconButton
onClick={handleToggleOptions}
variant="shadow"
size="small"
icon={<IconDotsVertical size={theme.icon.size.md} />}
/>
{isOptionsOpen && (
<DropdownMenu ref={refs.setFloating} style={floatingStyles}>
<DropdownMenuItemsContainer onClick={(e) => e.stopPropagation()}>
<DropdownMenuSelectableItem onClick={handleDetachPerson}>
<IconButton icon={<IconLinkOff size={14} />} size="small" />
Detach relation
</DropdownMenuSelectableItem>
</DropdownMenuItemsContainer>
</DropdownMenu>
)}
</div>
)}
</StyledCard> </StyledCard>
); );
} }

View File

@ -59,6 +59,19 @@ const simpleAbilityCheck: OperationAbilityChecker = async (
prisma, prisma,
data, 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 // Extract entity name from model name
const entity = camelCase(modelName); const entity = camelCase(modelName);
// Handle all operations cases // 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 // Check if user try to connect an element that is not allowed to read
for (const item of items) { 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))) { if (!ability.can(AbilityAction.Read, subject(modelName, item))) {
return false; return false;
} }