Add dueDate and assignee on notes (#988)

* Add dueDate and assignee on notes

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-29 15:36:21 -07:00
committed by GitHub
parent d9f6ae8663
commit 8601ed04ae
46 changed files with 875 additions and 205 deletions

View File

@ -0,0 +1,47 @@
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconUserCircle } from '@/ui/icon';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
import { UserChip } from '@/users/components/UserChip';
import { Company, User } from '~/generated/graphql';
import { ActivityAssigneeEditableFieldEditMode } from './ActivityAssigneeEditableFieldEditMode';
type OwnProps = {
activity: Pick<Company, 'id' | 'accountOwnerId'> & {
assignee?: Pick<User, 'id' | 'displayName' | 'avatarUrl'> | null;
};
};
export function ActivityAssigneeEditableField({ activity }: OwnProps) {
return (
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
label="Assignee"
iconLabel={<IconUserCircle />}
editModeContent={
<ActivityAssigneeEditableFieldEditMode activity={activity} />
}
displayModeContent={
activity.assignee?.displayName ? (
<UserChip
id={activity.assignee.id}
name={activity.assignee?.displayName ?? ''}
pictureUrl={activity.assignee?.avatarUrl ?? ''}
/>
) : (
<></>
)
}
isDisplayModeContentEmpty={!activity.assignee}
isDisplayModeFixHeight={true}
/>
</RecoilScope>
</RecoilScope>
);
}

View File

@ -0,0 +1,47 @@
import styled from '@emotion/styled';
import { ActivityAssigneePicker } from '@/activities/components/ActivityAssigneePicker';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { Activity, User } from '~/generated/graphql';
const StyledContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
export type OwnProps = {
activity: Pick<Activity, 'id'> & {
assignee?: Pick<User, 'id' | 'displayName'> | null;
};
onSubmit?: () => void;
onCancel?: () => void;
};
export function ActivityAssigneeEditableFieldEditMode({
activity,
onSubmit,
onCancel,
}: OwnProps) {
const { closeEditableField } = useEditableField();
function handleSubmit() {
closeEditableField();
onSubmit?.();
}
function handleCancel() {
closeEditableField();
onCancel?.();
}
return (
<StyledContainer>
<ActivityAssigneePicker
activity={activity}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>
</StyledContainer>
);
}

View File

@ -0,0 +1,102 @@
import styled from '@emotion/styled';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconArrowUpRight } from '@/ui/icon';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
import {
Activity,
ActivityTarget,
useGetCompaniesQuery,
useGetPeopleQuery,
} from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
import { ActivityRelationEditableFieldEditMode } from './ActivityRelationEditableFieldEditMode';
const StyledDisplayModeContainer = styled.div`
display: flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.spacing(1)};
`;
type OwnProps = {
activity?: Pick<Activity, 'id'> & {
activityTargets?: Array<
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
> | null;
};
};
export function ActivityRelationEditableField({ activity }: OwnProps) {
const { data: targetPeople } = useGetPeopleQuery({
variables: {
where: {
id: {
in: activity?.activityTargets
? activity?.activityTargets
.filter((target) => target.commentableType === 'Person')
.map((target) => target.commentableId ?? '')
: [],
},
},
},
});
const { data: targetCompanies } = useGetCompaniesQuery({
variables: {
where: {
id: {
in: activity?.activityTargets
? activity?.activityTargets
.filter((target) => target.commentableType === 'Company')
.map((target) => target.commentableId ?? '')
: [],
},
},
},
});
return (
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
useEditButton
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
iconLabel={<IconArrowUpRight />}
editModeContent={
<ActivityRelationEditableFieldEditMode activity={activity} />
}
label="Relations"
displayModeContent={
<StyledDisplayModeContainer>
{targetCompanies?.companies &&
targetCompanies.companies.map((company) => (
<CompanyChip
key={company.id}
id={company.id}
name={company.name}
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
/>
))}
{targetPeople?.people &&
targetPeople.people.map((person) => (
<PersonChip
key={person.id}
id={person.id}
name={person.displayName}
pictureUrl={person.avatarUrl ?? ''}
/>
))}
</StyledDisplayModeContainer>
}
/>
</RecoilScope>
</RecoilScope>
);
}

View File

@ -0,0 +1,123 @@
import { useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled';
import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange';
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '@/activities/utils/flatMapAndSortEntityForSelectArrayByName';
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
import { useFilteredSearchPeopleQuery } from '@/people/queries';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { MultipleEntitySelect } from '@/ui/relation-picker/components/MultipleEntitySelect';
import { Activity, ActivityTarget } from '~/generated/graphql';
import { assertNotNull } from '~/utils/assert';
type OwnProps = {
activity?: Pick<Activity, 'id'> & {
activityTargets?: Array<
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
> | null;
};
};
const StyledSelectContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
export function ActivityRelationEditableFieldEditMode({ activity }: OwnProps) {
const [searchFilter, setSearchFilter] = useState('');
const initialPeopleIds = useMemo(
() =>
activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Person')
.map((relation) => relation.commentableId)
.filter(assertNotNull) ?? [],
[activity?.activityTargets],
);
const initialCompanyIds = useMemo(
() =>
activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Company')
.map((relation) => relation.commentableId)
.filter(assertNotNull) ?? [],
[activity?.activityTargets],
);
const initialSelectedEntityIds = useMemo(
() =>
[...initialPeopleIds, ...initialCompanyIds].reduce<
Record<string, boolean>
>((result, entityId) => ({ ...result, [entityId]: true }), {}),
[initialPeopleIds, initialCompanyIds],
);
const [selectedEntityIds, setSelectedEntityIds] = useState<
Record<string, boolean>
>(initialSelectedEntityIds);
const personsForMultiSelect = useFilteredSearchPeopleQuery({
searchFilter,
selectedIds: initialPeopleIds,
});
const companiesForMultiSelect = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: initialCompanyIds,
});
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.selectedEntities,
companiesForMultiSelect.selectedEntities,
]);
const filteredSelectedEntities =
flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.filteredSelectedEntities,
companiesForMultiSelect.filteredSelectedEntities,
]);
const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.entitiesToSelect,
companiesForMultiSelect.entitiesToSelect,
]);
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activity,
});
const { closeEditableField } = useEditableField();
const handleSubmit = useCallback(() => {
handleCheckItemsChange(selectedEntityIds, entitiesToSelect);
closeEditableField();
}, [
handleCheckItemsChange,
selectedEntityIds,
entitiesToSelect,
closeEditableField,
]);
function handleCancel() {
closeEditableField();
}
return (
<StyledSelectContainer>
<MultipleEntitySelect
entities={{
entitiesToSelect,
filteredSelectedEntities,
selectedEntities,
loading: false,
}}
onChange={setSelectedEntityIds}
onSearchFilterChange={setSearchFilter}
searchFilter={searchFilter}
value={selectedEntityIds}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>
</StyledSelectContainer>
);
}