Fix Activity relation picker (#2684)

This commit is contained in:
Charles Bochet
2023-11-23 23:29:27 +01:00
committed by GitHub
parent 1e181c9d2a
commit de5b86ee66
8 changed files with 183 additions and 111 deletions

View File

@ -18,7 +18,7 @@ export const ActivityTargetChips = ({ targets }: { targets?: any }) => {
return ( return (
<StyledContainer> <StyledContainer>
{targets?.edges?.map(({ company, person }: any) => { {targets?.map(({ company, person }: any) => {
if (company) { if (company) {
return ( return (
<CompanyChip <CompanyChip

View File

@ -1,22 +1,13 @@
import { v4 } from 'uuid';
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';
import { ActivityTargetableEntityForSelect } from '../types/ActivityTargetableEntityForSelect';
export const useHandleCheckableActivityTargetChange = ({ export const useHandleCheckableActivityTargetChange = ({
activity, activityId,
currentActivityTargets,
}: { }: {
activity?: Pick<GraphQLActivity, 'id'> & { activityId: string;
activityTargets?: { currentActivityTargets: any[];
edges: Array<{
node: Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>;
}> | null;
};
};
}) => { }) => {
const { createOneObject } = useCreateOneObjectRecord<ActivityTarget>({ const { createOneObject } = useCreateOneObjectRecord<ActivityTarget>({
objectNameSingular: 'activityTarget', objectNameSingular: 'activityTarget',
@ -27,49 +18,56 @@ export const useHandleCheckableActivityTargetChange = ({
return async ( return async (
entityValues: Record<string, boolean>, entityValues: Record<string, boolean>,
entities: ActivityTargetableEntityForSelect[], entitiesToSelect: any,
selectedEntities: any,
) => { ) => {
if (!activity) { if (!activityId) {
return; return;
} }
const currentActivityTargetRecordIds = currentActivityTargets.map(
const currentEntityIds = activity.activityTargets?.edges ({ companyId, personId }) => companyId ?? personId ?? '',
? activity.activityTargets.edges.map(
({ node }) => node.personId ?? node.companyId,
)
: [];
const entitiesToAdd = entities.filter(
({ id }) => entityValues[id] && !currentEntityIds.includes(id),
); );
if (entitiesToAdd.length) { const idsToAdd = Object.entries(entityValues)
entitiesToAdd.map((entity) => { .filter(
([recordId, value]) =>
value && !currentActivityTargetRecordIds.includes(recordId),
)
.map(([id, _]) => id);
const idsToDelete = Object.entries(entityValues)
.filter(([_, value]) => !value)
.map(([id, _]) => id);
if (idsToAdd.length) {
idsToAdd.map((id) => {
const entityFromToSelect = entitiesToSelect.filter(
(entity: any) => entity.id === id,
).length
? entitiesToSelect.filter((entity: any) => entity.id === id)[0]
: null;
const entityFromSelected = selectedEntities.filter(
(entity: any) => entity.id === id,
).length
? selectedEntities.filter((entity: any) => entity.id === id)[0]
: null;
const entity = entityFromToSelect ?? entityFromSelected;
createOneObject?.({ createOneObject?.({
activityId: activity.id, activityId: activityId,
activityTargetInputs: { companyId: entity.record.__typename === 'Company' ? entity.id : null,
id: v4(), personId: entity.record.__typename === 'Person' ? entity.id : null,
createdAt: new Date().toISOString(),
companyId: entity.entityType === 'Company' ? entity.id : null,
personId: entity.entityType === 'Person' ? entity.id : null,
},
}); });
}); });
} }
const activityTargetIdsToDelete = activity.activityTargets?.edges if (idsToDelete.length) {
? activity.activityTargets.edges idsToDelete.map((id) => {
.filter( const currentActivityTargetId = currentActivityTargets.filter(
({ node }) => ({ companyId, personId }) => companyId === id || personId === id,
(node.personId ?? node.companyId) && )[0].id;
!entityValues[node.personId ?? node.companyId ?? ''], deleteOneObject?.(currentActivityTargetId);
)
.map(({ node }) => node.id)
: [];
if (activityTargetIdsToDelete.length) {
activityTargetIdsToDelete.map((id) => {
deleteOneObject?.(id);
}); });
} }
}; };

View File

@ -1,20 +1,20 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useQuery } from '@apollo/client';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange'; import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity'; import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '@/activities/utils/flatMapAndSortEntityForSelectArrayByName';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker';
import { MultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
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 ActivityTargetInlineCellEditModeProps = { type ActivityTargetInlineCellEditModeProps = {
activity?: Pick<GraphQLActivity, 'id'> & { activityId: string;
activityTargets?: { activityTargets: Array<Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>>;
edges: Array<{
node: Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>;
}> | null;
};
};
}; };
const StyledSelectContainer = styled.div` const StyledSelectContainer = styled.div`
@ -24,26 +24,27 @@ const StyledSelectContainer = styled.div`
`; `;
export const ActivityTargetInlineCellEditMode = ({ export const ActivityTargetInlineCellEditMode = ({
activity, activityId,
activityTargets,
}: ActivityTargetInlineCellEditModeProps) => { }: ActivityTargetInlineCellEditModeProps) => {
const [searchFilter, setSearchFilter] = useState(''); const [searchFilter, setSearchFilter] = useState('');
const initialPeopleIds = useMemo( const initialPeopleIds = useMemo(
() => () =>
activity?.activityTargets?.edges activityTargets
?.filter(({ node }) => node.personId !== null) ?.filter(({ personId }) => personId !== null)
.map(({ node }) => node.personId) .map(({ personId }) => personId)
.filter(assertNotNull) ?? [], .filter(assertNotNull) ?? [],
[activity?.activityTargets], [activityTargets],
); );
const initialCompanyIds = useMemo( const initialCompanyIds = useMemo(
() => () =>
activity?.activityTargets?.edges activityTargets
?.filter(({ node }) => node.companyId !== null) ?.filter(({ companyId }) => companyId !== null)
.map(({ node }) => node.companyId) .map(({ companyId }) => companyId)
.filter(assertNotNull) ?? [], .filter(assertNotNull) ?? [],
[activity?.activityTargets], [activityTargets],
); );
const initialSelectedEntityIds = useMemo( const initialSelectedEntityIds = useMemo(
@ -54,45 +55,92 @@ export const ActivityTargetInlineCellEditMode = ({
[initialPeopleIds, initialCompanyIds], [initialPeopleIds, initialCompanyIds],
); );
const { findManyQuery: findManyPeopleQuery } = useObjectMetadataItem({
objectNameSingular: 'person',
});
const { findManyQuery: findManyCompaniesQuery } = useObjectMetadataItem({
objectNameSingular: 'company',
});
const useFindManyPeopleQuery = (options: any) =>
useQuery(findManyPeopleQuery, options);
const useFindManyCompaniesQuery = (options: any) =>
useQuery(findManyCompaniesQuery, options);
const [selectedEntityIds, setSelectedEntityIds] = useState< const [selectedEntityIds, setSelectedEntityIds] = useState<
Record<string, boolean> Record<string, boolean>
>(initialSelectedEntityIds); >(initialSelectedEntityIds);
// const personsForMultiSelect = useFilteredSearchPeopleQuery({ const { identifiersMapper, searchQuery } = useRelationPicker();
// searchFilter,
// selectedIds: initialPeopleIds,
// });
// const companiesForMultiSelect = useFilteredSearchCompanyQuery({ const people = useFilteredSearchEntityQuery({
// searchFilter, queryHook: useFindManyPeopleQuery,
// selectedIds: initialCompanyIds, filters: [
// }); {
fieldNames: searchQuery?.computeFilterFields?.('person') ?? [],
filter: searchFilter,
},
],
orderByField: 'createdAt',
mappingFunction: (record: any) => identifiersMapper?.(record, 'person'),
selectedIds: initialPeopleIds,
objectNamePlural: 'people',
limit: 3,
});
// const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([ const companies = useFilteredSearchEntityQuery({
// personsForMultiSelect.selectedEntities, queryHook: useFindManyCompaniesQuery,
// companiesForMultiSelect.selectedEntities, filters: [
// ]); {
fieldNames: searchQuery?.computeFilterFields?.('company') ?? [],
filter: searchFilter,
},
],
orderByField: 'createdAt',
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
selectedIds: initialCompanyIds,
objectNamePlural: 'companies',
limit: 3,
});
// const filteredSelectedEntities = const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
// flatMapAndSortEntityForSelectArrayOfArrayByName([ people.selectedEntities,
// personsForMultiSelect.filteredSelectedEntities, companies.selectedEntities,
// companiesForMultiSelect.filteredSelectedEntities, ]);
// ]);
// const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([ const filteredSelectedEntities =
// personsForMultiSelect.entitiesToSelect, flatMapAndSortEntityForSelectArrayOfArrayByName([
// companiesForMultiSelect.entitiesToSelect, people.filteredSelectedEntities,
// ]); companies.filteredSelectedEntities,
]);
const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
people.entitiesToSelect,
companies.entitiesToSelect,
]);
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({ const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activity, activityId,
currentActivityTargets: activityTargets,
}); });
const { closeInlineCell: closeEditableField } = useInlineCell(); const { closeInlineCell: closeEditableField } = useInlineCell();
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
//handleCheckItemsChange(selectedEntityIds, entitiesToSelect); handleCheckItemsChange(
selectedEntityIds,
entitiesToSelect,
selectedEntities,
);
closeEditableField(); closeEditableField();
}, [closeEditableField]); }, [
closeEditableField,
entitiesToSelect,
handleCheckItemsChange,
selectedEntities,
selectedEntityIds,
]);
const handleCancel = () => { const handleCancel = () => {
closeEditableField(); closeEditableField();
@ -100,7 +148,7 @@ export const ActivityTargetInlineCellEditMode = ({
return ( return (
<StyledSelectContainer> <StyledSelectContainer>
{/* <MultipleEntitySelect <MultipleEntitySelect
entities={{ entities={{
entitiesToSelect, entitiesToSelect,
filteredSelectedEntities, filteredSelectedEntities,
@ -113,7 +161,7 @@ export const ActivityTargetInlineCellEditMode = ({
value={selectedEntityIds} value={selectedEntityIds}
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> */} />
</StyledSelectContainer> </StyledSelectContainer>
); );
}; };

View File

@ -2,8 +2,7 @@ import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips
import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode'; import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity'; import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { Company } from '@/companies/types/Company'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { Person } from '@/people/types/Person';
import { IconArrowUpRight, IconPencil } from '@/ui/display/icon'; import { IconArrowUpRight, IconPencil } from '@/ui/display/icon';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecordInlineCellContainer } from '@/ui/object/record-inline-cell/components/RecordInlineCellContainer'; import { RecordInlineCellContainer } from '@/ui/object/record-inline-cell/components/RecordInlineCellContainer';
@ -14,10 +13,7 @@ type ActivityTargetsInlineCellProps = {
activity?: Pick<GraphQLActivity, 'id'> & { activity?: Pick<GraphQLActivity, 'id'> & {
activityTargets?: { activityTargets?: {
edges: Array<{ edges: Array<{
node: Pick<ActivityTarget, 'id' | 'personId' | 'companyId'> & { node: Pick<ActivityTarget, 'id'>;
person?: Pick<Person, 'id' | 'name' | 'avatarUrl'> | null;
company?: Pick<Company, 'id' | 'domainName' | 'name'> | null;
};
}> | null; }> | null;
}; };
}; };
@ -26,6 +22,18 @@ type ActivityTargetsInlineCellProps = {
export const ActivityTargetsInlineCell = ({ export const ActivityTargetsInlineCell = ({
activity, activity,
}: ActivityTargetsInlineCellProps) => { }: ActivityTargetsInlineCellProps) => {
const activityTargetIds =
activity?.activityTargets?.edges?.map(
(activityTarget) => activityTarget.node.id,
) ?? [];
const { objects: activityTargets } = useFindManyObjectRecords<ActivityTarget>(
{
objectNamePlural: 'activityTargets',
filter: { id: { in: activityTargetIds } },
},
);
return ( return (
<RecoilScope CustomRecoilScopeContext={FieldRecoilScopeContext}> <RecoilScope CustomRecoilScopeContext={FieldRecoilScopeContext}>
<RecordInlineCellContainer <RecordInlineCellContainer
@ -35,12 +43,13 @@ export const ActivityTargetsInlineCell = ({
}} }}
IconLabel={IconArrowUpRight} IconLabel={IconArrowUpRight}
editModeContent={ editModeContent={
<ActivityTargetInlineCellEditMode activity={activity} /> <ActivityTargetInlineCellEditMode
activityId={activity?.id ?? ''}
activityTargets={activityTargets as any}
/>
} }
label="Relations" label="Relations"
displayModeContent={ displayModeContent={<ActivityTargetChips targets={activityTargets} />}
<ActivityTargetChips targets={activity?.activityTargets?.edges} />
}
isDisplayModeContentEmpty={ isDisplayModeContentEmpty={
activity?.activityTargets?.edges?.length === 0 activity?.activityTargets?.edges?.length === 0
} }

View File

@ -2,6 +2,7 @@ import { ReactElement } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { TaskRow } from './TaskRow'; import { TaskRow } from './TaskRow';
@ -60,7 +61,7 @@ export const TaskList = ({ title, tasks, button }: TaskListProps) => (
</StyledTitleBar> </StyledTitleBar>
<StyledTaskRows> <StyledTaskRows>
{tasks.map((task) => ( {tasks.map((task) => (
<TaskRow key={task.id} task={task} /> <TaskRow key={task.id} task={task as unknown as GraphQLActivity} />
))} ))}
</StyledTaskRows> </StyledTaskRows>
</StyledContainer> </StyledContainer>

View File

@ -3,7 +3,9 @@ import styled from '@emotion/styled';
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips'; import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { Activity } from '@/activities/types/Activity'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { IconCalendar, IconComment } from '@/ui/display/icon'; import { IconCalendar, IconComment } from '@/ui/display/icon';
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip'; import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox'; import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox';
@ -61,13 +63,29 @@ const StyledFieldsContainer = styled.div`
display: flex; display: flex;
`; `;
export const TaskRow = ({ task }: { task: Omit<Activity, 'assigneeId'> }) => { export const TaskRow = ({
task,
}: {
task: Omit<GraphQLActivity, 'assigneeId'>;
}) => {
const theme = useTheme(); const theme = useTheme();
const openActivityRightDrawer = useOpenActivityRightDrawer(); const openActivityRightDrawer = useOpenActivityRightDrawer();
const body = JSON.parse(task.body ?? '{}')[0]?.content[0]?.text; const body = JSON.parse(task.body ?? '{}')[0]?.content[0]?.text;
const { completeTask } = useCompleteTask(task); const { completeTask } = useCompleteTask(task);
const activityTargetIds =
task?.activityTargets?.edges.map(
(activityTarget) => activityTarget.node.id,
) ?? [];
const { objects: activityTargets } = useFindManyObjectRecords<ActivityTarget>(
{
objectNamePlural: 'activityTargets',
filter: { id: { in: activityTargetIds } },
},
);
return ( return (
<StyledContainer <StyledContainer
onClick={() => { onClick={() => {
@ -97,7 +115,7 @@ export const TaskRow = ({ task }: { task: Omit<Activity, 'assigneeId'> }) => {
)} )}
</StyledTaskBody> </StyledTaskBody>
<StyledFieldsContainer> <StyledFieldsContainer>
<ActivityTargetChips targets={task.activityTargets} /> <ActivityTargetChips targets={activityTargets} />
<StyledDueDate <StyledDueDate
isPast={ isPast={
!!task.dueAt && hasDatePassed(task.dueAt) && !task.completedAt !!task.dueAt && hasDatePassed(task.dueAt) && !task.completedAt

View File

@ -47,8 +47,8 @@ export const CommandMenu = () => {
objectNamePlural: 'people', objectNamePlural: 'people',
filter: { filter: {
or: [ or: [
{ name: { firstName: { like: `%${search}%` } } }, { name: { firstName: { ilike: `%${search}%` } } },
{ name: { firstName: { like: `%${search}%` } } }, { name: { firstName: { ilike: `%${search}%` } } },
], ],
}, },
limit: 3, limit: 3,
@ -58,7 +58,7 @@ export const CommandMenu = () => {
skip: !isCommandMenuOpened, skip: !isCommandMenuOpened,
objectNamePlural: 'companies', objectNamePlural: 'companies',
filter: { filter: {
name: { like: `%${search}%` }, name: { ilike: `%${search}%` },
}, },
limit: 3, limit: 3,
}); });

View File

@ -98,10 +98,8 @@ export const useFilteredSearchEntityQuery = ({
and: searchFilter, and: searchFilter,
}, },
{ {
not: { id: {
id: { in: selectedIds,
in: selectedIds,
},
}, },
}, },
], ],