Fix Activity Picker part 1 (#2678)

* Fix Activity Picker part 1

* Fix
This commit is contained in:
Charles Bochet
2023-11-23 16:25:13 +01:00
committed by GitHub
parent 033c3bc8b2
commit 72421a39ea
14 changed files with 174 additions and 102 deletions

View File

@ -4,9 +4,11 @@ import styled from '@emotion/styled';
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor'; import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
import { ActivityComments } from '@/activities/components/ActivityComments'; import { ActivityComments } from '@/activities/components/ActivityComments';
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown'; import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { Comment } from '@/activities/types/Comment'; import { Comment } from '@/activities/types/Comment';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { useFieldContext } from '@/object-record/hooks/useFieldContext'; import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell'; import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell';
@ -166,7 +168,9 @@ export const ActivityEditor = ({
</AssigneeFieldContextProvider> </AssigneeFieldContextProvider>
</> </>
)} )}
{/* <ActivityRelationEditableField activity={activity} /> */} <ActivityTargetsInlineCell
activity={activity as unknown as GraphQLActivity}
/>
</PropertyBox> </PropertyBox>
</StyledTopContainer> </StyledTopContainer>
<ActivityBodyEditor <ActivityBodyEditor

View File

@ -1,49 +0,0 @@
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { Company } from '@/companies/types/Company';
import { Person } from '@/people/types/Person';
import { IconArrowUpRight, IconPencil } from '@/ui/display/icon';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecordInlineCellContainer } from '@/ui/object/record-inline-cell/components/RecordInlineCellContainer';
import { FieldRecoilScopeContext } from '@/ui/object/record-inline-cell/states/recoil-scope-contexts/FieldRecoilScopeContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { ActivityRelationEditableFieldEditMode } from './ActivityRelationEditableFieldEditMode';
type ActivityRelationEditableFieldProps = {
activity?: Pick<Activity, 'id'> & {
activityTargets?: Array<
Pick<ActivityTarget, 'id' | 'personId' | 'companyId'> & {
person?: Pick<Person, 'id' | 'name' | 'avatarUrl'> | null;
company?: Pick<Company, 'id' | 'domainName' | 'name'> | null;
}
> | null;
};
};
export const ActivityRelationEditableField = ({
activity,
}: ActivityRelationEditableFieldProps) => {
return (
<RecoilScope CustomRecoilScopeContext={FieldRecoilScopeContext}>
<RecoilScope>
<RecordInlineCellContainer
buttonIcon={IconPencil}
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
IconLabel={IconArrowUpRight}
editModeContent={
<ActivityRelationEditableFieldEditMode activity={activity} />
}
label="Relations"
displayModeContent={
<ActivityTargetChips targets={activity?.activityTargets} />
}
isDisplayModeContentEmpty={activity?.activityTargets?.length === 0}
/>
</RecoilScope>
</RecoilScope>
);
};

View File

@ -1,7 +1,7 @@
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord'; import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord'; import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
@ -10,10 +10,12 @@ import { ActivityTargetableEntityForSelect } from '../types/ActivityTargetableEn
export const useHandleCheckableActivityTargetChange = ({ export const useHandleCheckableActivityTargetChange = ({
activity, activity,
}: { }: {
activity?: Pick<Activity, 'id'> & { activity?: Pick<GraphQLActivity, 'id'> & {
activityTargets?: Array< activityTargets?: {
Pick<ActivityTarget, 'id' | 'personId' | 'companyId'> edges: Array<{
> | null; node: Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>;
}> | null;
};
}; };
}) => { }) => {
const { createOneObject } = useCreateOneObjectRecord<ActivityTarget>({ const { createOneObject } = useCreateOneObjectRecord<ActivityTarget>({
@ -31,9 +33,9 @@ export const useHandleCheckableActivityTargetChange = ({
return; return;
} }
const currentEntityIds = activity.activityTargets const currentEntityIds = activity.activityTargets?.edges
? activity.activityTargets.map( ? activity.activityTargets.edges.map(
({ personId, companyId }) => personId ?? companyId, ({ node }) => node.personId ?? node.companyId,
) )
: []; : [];
@ -55,14 +57,14 @@ export const useHandleCheckableActivityTargetChange = ({
}); });
} }
const activityTargetIdsToDelete = activity.activityTargets const activityTargetIdsToDelete = activity.activityTargets?.edges
? activity.activityTargets ? activity.activityTargets.edges
.filter( .filter(
({ personId, companyId }) => ({ node }) =>
(personId ?? companyId) && (node.personId ?? node.companyId) &&
!entityValues[personId ?? companyId ?? ''], !entityValues[node.personId ?? node.companyId ?? ''],
) )
.map(({ id }) => id) .map(({ node }) => node.id)
: []; : [];
if (activityTargetIdsToDelete.length) { if (activityTargetIdsToDelete.length) {

View File

@ -2,16 +2,18 @@ import { useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange'; import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { useInlineCell } from '@/ui/object/record-inline-cell/hooks/useInlineCell'; import { useInlineCell } from '@/ui/object/record-inline-cell/hooks/useInlineCell';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
type ActivityRelationEditableFieldEditModeProps = { type ActivityTargetInlineCellEditModeProps = {
activity?: Pick<Activity, 'id'> & { activity?: Pick<GraphQLActivity, 'id'> & {
activityTargets?: Array< activityTargets?: {
Pick<ActivityTarget, 'id' | 'personId' | 'companyId'> edges: Array<{
> | null; node: Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>;
}> | null;
};
}; };
}; };
@ -21,25 +23,25 @@ const StyledSelectContainer = styled.div`
top: -8px; top: -8px;
`; `;
export const ActivityRelationEditableFieldEditMode = ({ export const ActivityTargetInlineCellEditMode = ({
activity, activity,
}: ActivityRelationEditableFieldEditModeProps) => { }: ActivityTargetInlineCellEditModeProps) => {
const [searchFilter, setSearchFilter] = useState(''); const [searchFilter, setSearchFilter] = useState('');
const initialPeopleIds = useMemo( const initialPeopleIds = useMemo(
() => () =>
activity?.activityTargets activity?.activityTargets?.edges
?.filter((relation) => relation.personId !== null) ?.filter(({ node }) => node.personId !== null)
.map((relation) => relation.personId) .map(({ node }) => node.personId)
.filter(assertNotNull) ?? [], .filter(assertNotNull) ?? [],
[activity?.activityTargets], [activity?.activityTargets],
); );
const initialCompanyIds = useMemo( const initialCompanyIds = useMemo(
() => () =>
activity?.activityTargets activity?.activityTargets?.edges
?.filter((relation) => relation.companyId !== null) ?.filter(({ node }) => node.companyId !== null)
.map((relation) => relation.companyId) .map(({ node }) => node.companyId)
.filter(assertNotNull) ?? [], .filter(assertNotNull) ?? [],
[activity?.activityTargets], [activity?.activityTargets],
); );

View File

@ -0,0 +1,50 @@
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { Company } from '@/companies/types/Company';
import { Person } from '@/people/types/Person';
import { IconArrowUpRight, IconPencil } from '@/ui/display/icon';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecordInlineCellContainer } from '@/ui/object/record-inline-cell/components/RecordInlineCellContainer';
import { FieldRecoilScopeContext } from '@/ui/object/record-inline-cell/states/recoil-scope-contexts/FieldRecoilScopeContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
type ActivityTargetsInlineCellProps = {
activity?: Pick<GraphQLActivity, 'id'> & {
activityTargets?: {
edges: Array<{
node: Pick<ActivityTarget, 'id' | 'personId' | 'companyId'> & {
person?: Pick<Person, 'id' | 'name' | 'avatarUrl'> | null;
company?: Pick<Company, 'id' | 'domainName' | 'name'> | null;
};
}> | null;
};
};
};
export const ActivityTargetsInlineCell = ({
activity,
}: ActivityTargetsInlineCellProps) => {
return (
<RecoilScope CustomRecoilScopeContext={FieldRecoilScopeContext}>
<RecordInlineCellContainer
buttonIcon={IconPencil}
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
IconLabel={IconArrowUpRight}
editModeContent={
<ActivityTargetInlineCellEditMode activity={activity} />
}
label="Relations"
displayModeContent={
<ActivityTargetChips targets={activity?.activityTargets?.edges} />
}
isDisplayModeContentEmpty={
activity?.activityTargets?.edges?.length === 0
}
/>
</RecoilScope>
);
};

View File

@ -2,8 +2,9 @@ import { useMemo } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ActivityRelationEditableField } from '@/activities/editable-fields/components/ActivityRelationEditableField';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { Note } from '@/activities/types/Note'; import { Note } from '@/activities/types/Note';
import { IconComment } from '@/ui/display/icon'; import { IconComment } from '@/ui/display/icon';
import { import {
@ -100,7 +101,9 @@ export const NoteCard = ({
<StyledCardContent>{body}</StyledCardContent> <StyledCardContent>{body}</StyledCardContent>
</StyledCardDetailsContainer> </StyledCardDetailsContainer>
<StyledFooter> <StyledFooter>
<ActivityRelationEditableField activity={note} /> <ActivityTargetsInlineCell
activity={note as unknown as GraphQLActivity}
/>
{note.comments && note.comments.length > 0 && ( {note.comments && note.comments.length > 0 && (
<StyledCommentIcon> <StyledCommentIcon>
<IconComment size={theme.icon.size.md} /> <IconComment size={theme.icon.size.md} />

View File

@ -0,0 +1,33 @@
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { Comment } from '@/activities/types/Comment';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
export type ActivityType = 'Task' | 'Note';
type ActivityTargetNode = {
node: ActivityTarget;
};
type CommentNode = {
node: Comment;
};
export type GraphQLActivity = {
__typename: 'Activity';
id: string;
createdAt: string;
updatedAt: string;
completedAt: string | null;
dueAt: string | null;
activityTargets: {
edges: ActivityTargetNode[];
};
type: ActivityType;
title: string;
body: string;
author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
authorId: string;
assignee: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
assigneeId: string | null;
comments: CommentNode[];
};

View File

@ -24,10 +24,10 @@ export const EMPTY_MUTATION = gql`
} }
`; `;
export const useObjectMetadataItem = ({ export const useObjectMetadataItem = (
objectNamePlural, { objectNamePlural, objectNameSingular }: ObjectMetadataItemIdentifier,
objectNameSingular, depth?: number,
}: ObjectMetadataItemIdentifier) => { ) => {
const objectMetadataItem = useRecoilValue( const objectMetadataItem = useRecoilValue(
objectMetadataItemFamilySelector({ objectMetadataItemFamilySelector({
objectNamePlural, objectNamePlural,
@ -43,10 +43,12 @@ export const useObjectMetadataItem = ({
const findManyQuery = useGenerateFindManyCustomObjectsQuery({ const findManyQuery = useGenerateFindManyCustomObjectsQuery({
objectMetadataItem, objectMetadataItem,
depth,
}); });
const findOneQuery = useGenerateFindOneCustomObjectQuery({ const findOneQuery = useGenerateFindOneCustomObjectQuery({
objectMetadataItem, objectMetadataItem,
depth,
}); });
const createOneMutation = useGenerateCreateOneObjectMutation({ const createOneMutation = useGenerateCreateOneObjectMutation({

View File

@ -9,19 +9,24 @@ export const useFindOneObjectRecord = <
objectNameSingular, objectNameSingular,
objectRecordId, objectRecordId,
onCompleted, onCompleted,
depth,
skip, skip,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'> & { }: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'> & {
objectRecordId: string | undefined; objectRecordId: string | undefined;
onCompleted?: (data: ObjectType) => void; onCompleted?: (data: ObjectType) => void;
skip?: boolean; skip?: boolean;
depth?: number;
}) => { }) => {
const { const {
objectMetadataItem: foundObjectMetadataItem, objectMetadataItem: foundObjectMetadataItem,
objectNotFoundInMetadata, objectNotFoundInMetadata,
findOneQuery, findOneQuery,
} = useObjectMetadataItem({ } = useObjectMetadataItem(
objectNameSingular, {
}); objectNameSingular,
},
depth,
);
const { data, loading, error } = useQuery< const { data, loading, error } = useQuery<
{ [nameSingular: string]: ObjectType }, { [nameSingular: string]: ObjectType },

View File

@ -7,8 +7,10 @@ import { capitalize } from '~/utils/string/capitalize';
export const useGenerateFindManyCustomObjectsQuery = ({ export const useGenerateFindManyCustomObjectsQuery = ({
objectMetadataItem, objectMetadataItem,
depth,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem | undefined | null;
depth?: number;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
@ -31,7 +33,7 @@ export const useGenerateFindManyCustomObjectsQuery = ({
node { node {
id id
${objectMetadataItem.fields ${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery(field)) .map((field) => mapFieldMetadataToGraphQLQuery(field, depth))
.join('\n')} .join('\n')}
} }
cursor cursor

View File

@ -6,8 +6,10 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useGenerateFindOneCustomObjectQuery = ({ export const useGenerateFindOneCustomObjectQuery = ({
objectMetadataItem, objectMetadataItem,
depth,
}: { }: {
objectMetadataItem: ObjectMetadataItem | null | undefined; objectMetadataItem: ObjectMetadataItem | null | undefined;
depth?: number;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
@ -24,7 +26,7 @@ export const useGenerateFindOneCustomObjectQuery = ({
}){ }){
id id
${objectMetadataItem.fields ${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery(field)) .map((field) => mapFieldMetadataToGraphQLQuery(field, depth))
.join('\n')} .join('\n')}
} }
} }

View File

@ -27,7 +27,7 @@ const StyledTag = styled.h3<{
background: ${({ color, theme }) => theme.tag.background[color]}; background: ${({ color, theme }) => theme.tag.background[color]};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ color, theme }) => theme.tag.text[color]}; color: ${({ color, theme }) => theme.tag.text[color]};
display: inline-block; display: flex;
flex-direction: row; flex-direction: row;
font-size: ${({ theme }) => theme.font.size.md}; font-size: ${({ theme }) => theme.font.size.md};
font-style: normal; font-style: normal;

View File

@ -29,9 +29,7 @@ const activityTargetMetadata = {
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'person', name: 'person',
label: 'Person', label: 'Person',
targetColumnMap: { targetColumnMap: {},
value: 'personId',
},
description: 'ActivityTarget person', description: 'ActivityTarget person',
icon: 'IconUser', icon: 'IconUser',
isNullable: true, isNullable: true,
@ -42,9 +40,7 @@ const activityTargetMetadata = {
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'company', name: 'company',
label: 'Company', label: 'Company',
targetColumnMap: { targetColumnMap: {},
value: 'companyId',
},
description: 'ActivityTarget company', description: 'ActivityTarget company',
icon: 'IconBuildingSkyscraper', icon: 'IconBuildingSkyscraper',
isNullable: true, isNullable: true,

View File

@ -32,26 +32,46 @@ const commentMetadata = {
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'author', name: 'author',
label: 'Author', label: 'Author',
targetColumnMap: { targetColumnMap: {},
value: 'authorId',
},
description: 'Comment author', description: 'Comment author',
icon: 'IconCircleUser', icon: 'IconCircleUser',
isNullable: true, isNullable: true,
}, },
{
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'authorId',
label: 'Author',
targetColumnMap: {},
description: 'Comment author',
icon: 'IconCircleUser',
isNullable: true,
isSystem: true,
},
{ {
isCustom: false, isCustom: false,
isActive: true, isActive: true,
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'activity', name: 'activity',
label: 'Activity', label: 'Activity',
targetColumnMap: { targetColumnMap: {},
value: 'activityId',
},
description: 'Comment activity', description: 'Comment activity',
icon: 'IconNotes', icon: 'IconNotes',
isNullable: true, isNullable: true,
}, },
{
isCustom: false,
isActive: true,
type: FieldMetadataType.UUID,
name: 'activityId',
label: 'Activity',
targetColumnMap: {},
description: 'Comment activity',
icon: 'IconNotes',
isNullable: true,
isSystem: true,
},
], ],
}; };