mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 00:52:21 +03:00
Add dueDate and assignee on notes (#988)
* Add dueDate and assignee on notes * Fix tests * Fix tests
This commit is contained in:
parent
d9f6ae8663
commit
8601ed04ae
@ -2236,14 +2236,14 @@ export type GetActivitiesByTargetsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetActivitiesByTargetsQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null }> | null }> };
|
||||
export type GetActivitiesByTargetsQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null }> | null }> };
|
||||
|
||||
export type GetActivityQueryVariables = Exact<{
|
||||
activityId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetActivityQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null }> | null }> };
|
||||
export type GetActivityQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null }> | null }> };
|
||||
|
||||
export type AddActivityTargetsOnActivityMutationVariables = Exact<{
|
||||
activityId: Scalars['String'];
|
||||
@ -2269,15 +2269,12 @@ export type DeleteActivityMutationVariables = Exact<{
|
||||
export type DeleteActivityMutation = { __typename?: 'Mutation', deleteManyActivities: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type UpdateActivityMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
type?: InputMaybe<ActivityType>;
|
||||
completedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
where: ActivityWhereUniqueInput;
|
||||
data: ActivityUpdateInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateActivityMutation = { __typename?: 'Mutation', updateOneActivity: { __typename?: 'Activity', id: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null } };
|
||||
export type UpdateActivityMutation = { __typename?: 'Mutation', updateOneActivity: { __typename?: 'Activity', id: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string } | null } };
|
||||
|
||||
export type UploadAttachmentMutationVariables = Exact<{
|
||||
file: Scalars['Upload'];
|
||||
@ -2775,6 +2772,13 @@ export const GetActivitiesByTargetsDocument = gql`
|
||||
body
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
author {
|
||||
id
|
||||
firstName
|
||||
@ -2840,6 +2844,13 @@ export const GetActivityDocument = gql`
|
||||
title
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
author {
|
||||
id
|
||||
firstName
|
||||
@ -3021,16 +3032,20 @@ export type DeleteActivityMutationHookResult = ReturnType<typeof useDeleteActivi
|
||||
export type DeleteActivityMutationResult = Apollo.MutationResult<DeleteActivityMutation>;
|
||||
export type DeleteActivityMutationOptions = Apollo.BaseMutationOptions<DeleteActivityMutation, DeleteActivityMutationVariables>;
|
||||
export const UpdateActivityDocument = gql`
|
||||
mutation UpdateActivity($id: String!, $body: String, $title: String, $type: ActivityType, $completedAt: DateTime) {
|
||||
updateOneActivity(
|
||||
where: {id: $id}
|
||||
data: {body: $body, title: $title, type: $type, completedAt: $completedAt}
|
||||
) {
|
||||
mutation UpdateActivity($where: ActivityWhereUniqueInput!, $data: ActivityUpdateInput!) {
|
||||
updateOneActivity(where: $where, data: $data) {
|
||||
id
|
||||
body
|
||||
title
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -3049,11 +3064,8 @@ export type UpdateActivityMutationFn = Apollo.MutationFunction<UpdateActivityMut
|
||||
* @example
|
||||
* const [updateActivityMutation, { data, loading, error }] = useUpdateActivityMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* body: // value for 'body'
|
||||
* title: // value for 'title'
|
||||
* type: // value for 'type'
|
||||
* completedAt: // value for 'completedAt'
|
||||
* where: // value for 'where'
|
||||
* data: // value for 'data'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
@ -0,0 +1,79 @@
|
||||
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { SingleEntitySelect } from '@/ui/relation-picker/components/SingleEntitySelect';
|
||||
import { relationPickerSearchFilterScopedState } from '@/ui/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
|
||||
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
|
||||
import {
|
||||
Activity,
|
||||
User,
|
||||
useSearchUserQuery,
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export type OwnProps = {
|
||||
activity: Pick<Activity, 'id'> & {
|
||||
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
|
||||
};
|
||||
onSubmit?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
type UserForSelect = EntityForSelect & {
|
||||
entityType: Entity.User;
|
||||
};
|
||||
|
||||
export function ActivityAssigneePicker({
|
||||
activity,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: OwnProps) {
|
||||
const [searchFilter] = useRecoilScopedState(
|
||||
relationPickerSearchFilterScopedState,
|
||||
);
|
||||
const [updateActivity] = useUpdateActivityMutation();
|
||||
|
||||
const companies = useFilteredSearchEntityQuery({
|
||||
queryHook: useSearchUserQuery,
|
||||
selectedIds: activity?.accountOwner?.id ? [activity?.accountOwner?.id] : [],
|
||||
searchFilter: searchFilter,
|
||||
mappingFunction: (user) => ({
|
||||
entityType: Entity.User,
|
||||
id: user.id,
|
||||
name: user.displayName,
|
||||
avatarType: 'rounded',
|
||||
avatarUrl: user.avatarUrl ?? '',
|
||||
}),
|
||||
orderByField: 'firstName',
|
||||
searchOnFields: ['firstName', 'lastName'],
|
||||
});
|
||||
|
||||
async function handleEntitySelected(
|
||||
selectedUser: UserForSelect | null | undefined,
|
||||
) {
|
||||
if (selectedUser) {
|
||||
await updateActivity({
|
||||
variables: {
|
||||
where: { id: activity.id },
|
||||
data: {
|
||||
assignee: { connect: { id: selectedUser.id } },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit?.();
|
||||
}
|
||||
|
||||
return (
|
||||
<SingleEntitySelect
|
||||
onEntitySelected={handleEntitySelected}
|
||||
onCancel={onCancel}
|
||||
entities={{
|
||||
loading: companies.loading,
|
||||
entitiesToSelect: companies.entitiesToSelect,
|
||||
selectedEntity: companies.selectedEntities[0],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -35,8 +35,12 @@ export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
|
||||
setBody(activityBody);
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: activity.id,
|
||||
body: activityBody,
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
body: activityBody,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
|
@ -4,20 +4,23 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
||||
import { ActivityComments } from '@/activities/components/ActivityComments';
|
||||
import { ActivityRelationPicker } from '@/activities/components/ActivityRelationPicker';
|
||||
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
||||
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconArrowUpRight } from '@/ui/icon/index';
|
||||
import { IconCalendar } from '@/ui/icon/index';
|
||||
import {
|
||||
Activity,
|
||||
ActivityTarget,
|
||||
ActivityType,
|
||||
User,
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { ActivityAssigneeEditableField } from '../editable-fields/components/ActivityAssigneeEditableField';
|
||||
import { ActivityRelationEditableField } from '../editable-fields/components/ActivityRelationEditableField';
|
||||
import { ActivityActionBar } from '../right-drawer/components/ActivityActionBar';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
@ -65,8 +68,16 @@ const StyledTopActionsContainer = styled.div`
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
activity: Pick<Activity, 'id' | 'title' | 'body' | 'type' | 'completedAt'> & {
|
||||
activity: Pick<
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'type' | 'completedAt' | 'dueAt'
|
||||
> & {
|
||||
comments?: Array<CommentForDrawer> | null;
|
||||
} & {
|
||||
assignee?: Pick<
|
||||
User,
|
||||
'id' | 'firstName' | 'lastName' | 'displayName'
|
||||
> | null;
|
||||
} & {
|
||||
activityTargets?: Array<
|
||||
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
@ -95,8 +106,12 @@ export function ActivityEditor({
|
||||
(newTitle: string) => {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: activity.id,
|
||||
title: newTitle ?? '',
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
@ -108,8 +123,12 @@ export function ActivityEditor({
|
||||
(value: boolean) => {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: activity.id,
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
@ -152,18 +171,29 @@ export function ActivityEditor({
|
||||
onCompletionChange={handleActivityCompletionChange}
|
||||
/>
|
||||
<PropertyBox>
|
||||
<PropertyBoxItem
|
||||
icon={<IconArrowUpRight />}
|
||||
value={
|
||||
<ActivityRelationPicker
|
||||
activity={{
|
||||
id: activity.id,
|
||||
activityTargets: activity.activityTargets ?? [],
|
||||
{activity.type === ActivityType.Task && (
|
||||
<>
|
||||
<DateEditableField
|
||||
value={activity.dueAt}
|
||||
icon={<IconCalendar />}
|
||||
label="Due date"
|
||||
onSubmit={(newDate) => {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
dueAt: newDate,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Relations"
|
||||
/>
|
||||
<ActivityAssigneeEditableField activity={activity} />
|
||||
</>
|
||||
)}
|
||||
<ActivityRelationEditableField activity={activity} />
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<ActivityBodyEditor
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -10,16 +10,16 @@ import {
|
||||
useRemoveActivityTargetsOnActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '../queries';
|
||||
import { GET_ACTIVITY } from '../queries';
|
||||
import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect';
|
||||
|
||||
export function useHandleCheckableActivityTargetChange({
|
||||
activity,
|
||||
}: {
|
||||
activity?: Pick<Activity, 'id'> & {
|
||||
activityTargets: Array<
|
||||
activityTargets?: Array<
|
||||
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
>;
|
||||
> | null;
|
||||
};
|
||||
}) {
|
||||
const [addActivityTargetsOnActivity] =
|
||||
@ -27,7 +27,7 @@ export function useHandleCheckableActivityTargetChange({
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
getOperationName(GET_ACTIVITY) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@ export function useHandleCheckableActivityTargetChange({
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
getOperationName(GET_ACTIVITY) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
@ -48,9 +48,9 @@ export function useHandleCheckableActivityTargetChange({
|
||||
return;
|
||||
}
|
||||
|
||||
const currentEntityIds = activity.activityTargets.map(
|
||||
({ commentableId }) => commentableId,
|
||||
);
|
||||
const currentEntityIds = activity.activityTargets
|
||||
? activity.activityTargets.map(({ commentableId }) => commentableId)
|
||||
: [];
|
||||
|
||||
const entitiesToAdd = entities.filter(
|
||||
({ id }) => entityValues[id] && !currentEntityIds.includes(id),
|
||||
@ -70,10 +70,13 @@ export function useHandleCheckableActivityTargetChange({
|
||||
});
|
||||
|
||||
const activityTargetIdsToDelete = activity.activityTargets
|
||||
.filter(
|
||||
({ commentableId }) => commentableId && !entityValues[commentableId],
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
? activity.activityTargets
|
||||
.filter(
|
||||
({ commentableId }) =>
|
||||
commentableId && !entityValues[commentableId],
|
||||
)
|
||||
.map(({ id }) => id)
|
||||
: [];
|
||||
|
||||
if (activityTargetIdsToDelete.length)
|
||||
await removeActivityTargetsOnActivity({
|
||||
|
@ -17,6 +17,13 @@ export const GET_ACTIVITIES_BY_TARGETS = gql`
|
||||
body
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
author {
|
||||
id
|
||||
firstName
|
||||
@ -54,6 +61,13 @@ export const GET_ACTIVITY = gql`
|
||||
title
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
author {
|
||||
id
|
||||
firstName
|
||||
|
@ -58,26 +58,22 @@ export const DELETE_ACTIVITY = gql`
|
||||
|
||||
export const UPDATE_ACTIVITY = gql`
|
||||
mutation UpdateActivity(
|
||||
$id: String!
|
||||
$body: String
|
||||
$title: String
|
||||
$type: ActivityType
|
||||
$completedAt: DateTime
|
||||
$where: ActivityWhereUniqueInput!
|
||||
$data: ActivityUpdateInput!
|
||||
) {
|
||||
updateOneActivity(
|
||||
where: { id: $id }
|
||||
data: {
|
||||
body: $body
|
||||
title: $title
|
||||
type: $type
|
||||
completedAt: $completedAt
|
||||
}
|
||||
) {
|
||||
updateOneActivity(where: $where, data: $data) {
|
||||
id
|
||||
body
|
||||
title
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -21,7 +21,7 @@ type OwnProps = {
|
||||
autoFillTitle?: boolean;
|
||||
};
|
||||
|
||||
export function Activity({
|
||||
export function RightDrawerActivity({
|
||||
activityId,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
@ -5,7 +5,7 @@ import { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
|
||||
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
||||
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
||||
|
||||
import { Activity } from '../Activity';
|
||||
import { RightDrawerActivity } from '../RightDrawerActivity';
|
||||
|
||||
export function RightDrawerCreateActivity() {
|
||||
const activityId = useRecoilValue(viewableActivityIdState);
|
||||
@ -15,7 +15,7 @@ export function RightDrawerCreateActivity() {
|
||||
<RightDrawerTopBar />
|
||||
<RightDrawerBody>
|
||||
{activityId && (
|
||||
<Activity
|
||||
<RightDrawerActivity
|
||||
activityId={activityId}
|
||||
showComment={false}
|
||||
autoFillTitle={true}
|
||||
|
@ -5,7 +5,7 @@ import { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
|
||||
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
||||
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
||||
|
||||
import { Activity } from '../Activity';
|
||||
import { RightDrawerActivity } from '../RightDrawerActivity';
|
||||
|
||||
export function RightDrawerEditActivity() {
|
||||
const activityId = useRecoilValue(viewableActivityIdState);
|
||||
@ -14,7 +14,7 @@ export function RightDrawerEditActivity() {
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar />
|
||||
<RightDrawerBody>
|
||||
{activityId && <Activity activityId={activityId} />}
|
||||
{activityId && <RightDrawerActivity activityId={activityId} />}
|
||||
</RightDrawerBody>
|
||||
</RightDrawerPage>
|
||||
);
|
||||
|
@ -7,12 +7,13 @@ import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRi
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||
import { IconNotes } from '@/ui/icon';
|
||||
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
import { Activity, User, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
import {
|
||||
beautifyExactDate,
|
||||
beautifyPastDateRelativeToNow,
|
||||
} from '~/utils/date-utils';
|
||||
|
||||
import { TimelineActivityCardFooter } from './TimelineActivityCardFooter';
|
||||
import { TimelineActivityTitle } from './TimelineActivityTitle';
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
@ -73,19 +74,19 @@ const StyledCard = styled.div`
|
||||
align-items: flex-start;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
max-width: 400px;
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
width: 400px;
|
||||
`;
|
||||
|
||||
const StyledCardContent = styled.div`
|
||||
align-self: stretch;
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -104,6 +105,11 @@ const StyledTooltip = styled(Tooltip)`
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCardDetailsContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTimelineItemContainer = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
@ -115,7 +121,9 @@ type OwnProps = {
|
||||
activity: Pick<
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'createdAt' | 'completedAt' | 'type'
|
||||
> & { author: Pick<Activity['author'], 'displayName'> };
|
||||
> & { author: Pick<Activity['author'], 'displayName'> } & {
|
||||
assignee?: Pick<User, 'id' | 'displayName'> | null;
|
||||
};
|
||||
};
|
||||
|
||||
export function TimelineActivity({ activity }: OwnProps) {
|
||||
@ -130,8 +138,10 @@ export function TimelineActivity({ activity }: OwnProps) {
|
||||
(value: boolean) => {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: activity.id,
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
where: { id: activity.id },
|
||||
data: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
@ -165,15 +175,20 @@ export function TimelineActivity({ activity }: OwnProps) {
|
||||
</StyledVerticalLineContainer>
|
||||
<StyledCardContainer>
|
||||
<StyledCard onClick={() => openActivityRightDrawer(activity.id)}>
|
||||
<TimelineActivityTitle
|
||||
title={activity.title ?? ''}
|
||||
completed={!!activity.completedAt}
|
||||
type={activity.type}
|
||||
onCompletionChange={handleActivityCompletionChange}
|
||||
/>
|
||||
<StyledCardContent>
|
||||
<OverflowingTextWithTooltip text={body ? body : '(No content)'} />
|
||||
</StyledCardContent>
|
||||
<StyledCardDetailsContainer>
|
||||
<TimelineActivityTitle
|
||||
title={activity.title ?? ''}
|
||||
completed={!!activity.completedAt}
|
||||
type={activity.type}
|
||||
onCompletionChange={handleActivityCompletionChange}
|
||||
/>
|
||||
<StyledCardContent>
|
||||
<OverflowingTextWithTooltip
|
||||
text={body ? body : '(No content)'}
|
||||
/>
|
||||
</StyledCardContent>
|
||||
</StyledCardDetailsContainer>
|
||||
<TimelineActivityCardFooter activity={activity} />
|
||||
</StyledCard>
|
||||
</StyledCardContainer>
|
||||
</StyledTimelineItemContainer>
|
||||
|
@ -0,0 +1,50 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { UserChip } from '@/users/components/UserChip';
|
||||
import { Activity, User } from '~/generated/graphql';
|
||||
import { beautifyExactDate } from '~/utils/date-utils';
|
||||
|
||||
type OwnProps = {
|
||||
activity: Pick<Activity, 'id' | 'dueAt'> & {
|
||||
assignee?: Pick<User, 'id' | 'displayName' | 'avatarUrl'> | null;
|
||||
};
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
const StyledVerticalSeparator = styled.div`
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
export function TimelineActivityCardFooter({ activity }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
{(activity.assignee || activity.dueAt) && (
|
||||
<StyledContainer>
|
||||
{activity.assignee && (
|
||||
<UserChip
|
||||
id={activity.assignee.id}
|
||||
name={activity.assignee.displayName ?? ''}
|
||||
pictureUrl={activity.assignee.avatarUrl ?? ''}
|
||||
/>
|
||||
)}
|
||||
{activity.dueAt && (
|
||||
<>
|
||||
{activity.assignee && <StyledVerticalSeparator />}
|
||||
{beautifyExactDate(activity.dueAt)}
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -12,15 +12,19 @@ const StyledTitleContainer = styled.div`
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(6)});
|
||||
`;
|
||||
|
||||
const StyledTitleText = styled.div<{ completed?: boolean }>`
|
||||
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledCheckboxContainer = styled.div`
|
||||
const StyledTitleText = styled.div<{
|
||||
completed?: boolean;
|
||||
hasCheckbox?: boolean;
|
||||
}>`
|
||||
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
|
||||
width: ${({ hasCheckbox, theme }) =>
|
||||
!hasCheckbox ? '100%;' : `calc(100% - ${theme.spacing(5)});`};
|
||||
`;
|
||||
|
||||
const StyledCheckboxContainer = styled.div<{ hasCheckbox?: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -55,7 +59,10 @@ export function TimelineActivityTitle({
|
||||
/>
|
||||
</StyledCheckboxContainer>
|
||||
)}
|
||||
<StyledTitleText completed={completed}>
|
||||
<StyledTitleText
|
||||
completed={completed}
|
||||
hasCheckbox={type === ActivityType.Task}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={title ? title : '(No title)'} />
|
||||
</StyledTitleText>
|
||||
</StyledTitleContainer>
|
||||
|
@ -99,7 +99,9 @@ const StyledCheckboxContainer = styled.div`
|
||||
`;
|
||||
|
||||
const StyledFieldContainer = styled.div`
|
||||
width: max-content;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function CompanyBoardCard() {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
IconBrandLinkedin,
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconLink,
|
||||
@ -81,7 +82,7 @@ export const companyViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
|
||||
{
|
||||
id: 'linkedin',
|
||||
columnLabel: 'LinkedIn',
|
||||
columnIcon: <IconMap />,
|
||||
columnIcon: <IconBrandLinkedin />,
|
||||
columnSize: 170,
|
||||
columnOrder: 6,
|
||||
metadata: {
|
||||
|
@ -38,6 +38,7 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
|
||||
)
|
||||
}
|
||||
isDisplayModeContentEmpty={!company.accountOwner}
|
||||
isDisplayModeFixHeight={true}
|
||||
/>
|
||||
</RecoilScope>
|
||||
</RecoilScope>
|
||||
|
@ -4,8 +4,8 @@ import { CompanyAccountOwnerPicker } from '@/companies/components/CompanyAccount
|
||||
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
|
||||
import { Company, User } from '~/generated/graphql';
|
||||
|
||||
const CompanyAccountOwnerPickerContainer = styled.div`
|
||||
left: 24px;
|
||||
const StyledContainer = styled.div`
|
||||
left: 0px;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
`;
|
||||
@ -36,12 +36,12 @@ export function CompanyAccountOwnerPickerFieldEditMode({
|
||||
}
|
||||
|
||||
return (
|
||||
<CompanyAccountOwnerPickerContainer>
|
||||
<StyledContainer>
|
||||
<CompanyAccountOwnerPicker
|
||||
company={company}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</CompanyAccountOwnerPickerContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
|
||||
|
||||
@ -10,6 +9,30 @@ type OwnProps = {
|
||||
company: Pick<Company, 'id' | 'name'>;
|
||||
};
|
||||
|
||||
const StyledEditableTitleInput = styled.input<{
|
||||
value: string;
|
||||
}>`
|
||||
background: transparent;
|
||||
|
||||
border: none;
|
||||
color: ${({ theme, value }) =>
|
||||
value ? theme.font.color.primary : theme.font.color.light};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
justify-content: center;
|
||||
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
outline: none;
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
}
|
||||
text-align: center;
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
`;
|
||||
|
||||
export function CompanyNameEditableField({ company }: OwnProps) {
|
||||
const [internalValue, setInternalValue] = useState(company.name);
|
||||
|
||||
@ -36,27 +59,14 @@ export function CompanyNameEditableField({ company }: OwnProps) {
|
||||
});
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
setInternalValue(company.name);
|
||||
}
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldContext}>
|
||||
<EditableField
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
editModeContent={
|
||||
<InplaceInputText
|
||||
placeholder={'Name'}
|
||||
autoFocus
|
||||
value={internalValue}
|
||||
onChange={(newValue: string) => {
|
||||
handleChange(newValue);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
displayModeContent={internalValue ?? ''}
|
||||
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||
<StyledEditableTitleInput
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
onChange={(event) => handleChange(event.target.value)}
|
||||
onBlur={handleSubmit}
|
||||
value={internalValue}
|
||||
/>
|
||||
</RecoilScope>
|
||||
);
|
||||
|
@ -33,6 +33,7 @@ export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
|
||||
secondValueFieldName: 'lastName',
|
||||
firstValuePlaceholder: 'First name',
|
||||
secondValuePlaceholder: 'Last name',
|
||||
avatarUrlFieldName: 'avatarUrl',
|
||||
entityType: Entity.Person,
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>,
|
||||
|
@ -41,6 +41,7 @@ export function PeopleCompanyEditableField({ people }: OwnProps) {
|
||||
)
|
||||
}
|
||||
isDisplayModeContentEmpty={!people.company}
|
||||
isDisplayModeFixHeight
|
||||
/>
|
||||
</RecoilScope>
|
||||
</RecoilScope>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
|
||||
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
|
||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||
@ -14,6 +16,12 @@ export type OwnProps = {
|
||||
people: Pick<Person, 'id'> & { company?: Pick<Company, 'id'> | null };
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
left: 0px;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
`;
|
||||
|
||||
export function PeopleCompanyEditableFieldEditMode({ people }: OwnProps) {
|
||||
const { closeEditableField } = useEditableField();
|
||||
|
||||
@ -51,14 +59,16 @@ export function PeopleCompanyEditableFieldEditMode({ people }: OwnProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SingleEntitySelect
|
||||
onEntitySelected={handleEntitySelected}
|
||||
entities={{
|
||||
entitiesToSelect: companies.entitiesToSelect,
|
||||
selectedEntity: companies.selectedEntities[0],
|
||||
loading: companies.loading,
|
||||
}}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
<StyledContainer>
|
||||
<SingleEntitySelect
|
||||
onEntitySelected={handleEntitySelected}
|
||||
entities={{
|
||||
entitiesToSelect: companies.entitiesToSelect,
|
||||
selectedEntity: companies.selectedEntities[0],
|
||||
loading: companies.loading,
|
||||
}}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
import { Person, useUpdateOnePersonMutation } from '~/generated/graphql';
|
||||
@ -21,56 +20,40 @@ export function PeopleFullNameEditableField({ people }: OwnProps) {
|
||||
|
||||
const [updatePeople] = useUpdateOnePersonMutation();
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValueFirstName(people.firstName);
|
||||
setInternalValueLastName(people.lastName);
|
||||
}, [people.firstName, people.lastName]);
|
||||
|
||||
async function handleChange(
|
||||
newValueFirstName: string,
|
||||
newValueLastName: string,
|
||||
) {
|
||||
setInternalValueFirstName(newValueFirstName);
|
||||
setInternalValueLastName(newValueLastName);
|
||||
handleSubmit(newValueFirstName, newValueLastName);
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit(
|
||||
newValueFirstName: string,
|
||||
newValueLastName: string,
|
||||
) {
|
||||
await updatePeople({
|
||||
variables: {
|
||||
where: {
|
||||
id: people.id,
|
||||
},
|
||||
data: {
|
||||
firstName: internalValueFirstName ?? '',
|
||||
lastName: internalValueLastName ?? '',
|
||||
firstName: newValueFirstName ?? '',
|
||||
lastName: newValueLastName ?? '',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
setInternalValueFirstName(people.firstName);
|
||||
setInternalValueLastName(people.lastName);
|
||||
}
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldContext}>
|
||||
<EditableField
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
editModeContent={
|
||||
<InplaceInputDoubleText
|
||||
firstValuePlaceholder={'First name'}
|
||||
secondValuePlaceholder={'Last name'}
|
||||
firstValue={internalValueFirstName ?? ''}
|
||||
secondValue={internalValueLastName ?? ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
}
|
||||
displayModeContent={`${internalValueFirstName} ${internalValueLastName}`}
|
||||
isDisplayModeContentEmpty={
|
||||
!(internalValueFirstName !== '') && !(internalValueLastName !== '')
|
||||
}
|
||||
<InplaceInputDoubleText
|
||||
firstValuePlaceholder={'First name'}
|
||||
secondValuePlaceholder={'Last name'}
|
||||
firstValue={internalValueFirstName ?? ''}
|
||||
secondValue={internalValueLastName ?? ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</RecoilScope>
|
||||
);
|
||||
|
@ -45,6 +45,7 @@ export function PipelineProgressPointOfContactEditableField({
|
||||
)
|
||||
}
|
||||
isDisplayModeContentEmpty={!pipelineProgress.pointOfContact}
|
||||
isDisplayModeFixHeight
|
||||
/>
|
||||
</RecoilScope>
|
||||
</RecoilScope>
|
||||
|
@ -5,7 +5,7 @@ import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
|
||||
import { Person, PipelineProgress } from '~/generated/graphql';
|
||||
|
||||
const PipelineProgressPointOfContactPickerContainer = styled.div`
|
||||
left: 24px;
|
||||
left: 0px;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
`;
|
||||
|
@ -77,7 +77,14 @@ export function DeleteModal({
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<StyledModal isOpen={isOpen} onOutsideClick={() => setIsOpen(!isOpen)}>
|
||||
<StyledModal
|
||||
isOpen={isOpen}
|
||||
onOutsideClick={() => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledSubtitle>{subtitle}</StyledSubtitle>
|
||||
<TextInput
|
||||
|
@ -15,11 +15,11 @@ const StyledRawLink = styled(RawLink)`
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
value: string;
|
||||
value: string | null;
|
||||
};
|
||||
|
||||
export function InplaceInputPhoneDisplayMode({ value }: OwnProps) {
|
||||
return isValidPhoneNumber(value) ? (
|
||||
return value && isValidPhoneNumber(value) ? (
|
||||
<StyledRawLink
|
||||
href={parsePhoneNumber(value, 'FR')?.getURI()}
|
||||
onClick={(event: MouseEvent<HTMLElement>) => {
|
||||
|
@ -15,6 +15,7 @@ const StyledIconContainer = styled.div`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
width: 16px;
|
||||
|
||||
svg {
|
||||
align-items: center;
|
||||
@ -32,6 +33,12 @@ const StyledLabelAndIconContainer = styled.div`
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledValueContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
max-width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div<Pick<OwnProps, 'labelFixedWidth'>>`
|
||||
align-items: center;
|
||||
|
||||
@ -39,18 +46,22 @@ const StyledLabel = styled.div<Pick<OwnProps, 'labelFixedWidth'>>`
|
||||
labelFixedWidth ? `${labelFixedWidth}px` : 'fit-content'};
|
||||
`;
|
||||
|
||||
const StyledEditButtonContainer = styled(motion.div)`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
`;
|
||||
|
||||
export const EditableFieldBaseContainer = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: 24px;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -66,6 +77,7 @@ type OwnProps = {
|
||||
parentHotkeyScope?: HotkeyScope;
|
||||
customEditHotkeyScope?: HotkeyScope;
|
||||
isDisplayModeContentEmpty?: boolean;
|
||||
isDisplayModeFixHeight?: boolean;
|
||||
onSubmit?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
@ -82,6 +94,7 @@ export function EditableField({
|
||||
disableHoverEffect,
|
||||
isDisplayModeContentEmpty,
|
||||
displayModeContentOnly,
|
||||
isDisplayModeFixHeight,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: OwnProps) {
|
||||
@ -119,29 +132,32 @@ export function EditableField({
|
||||
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
|
||||
)}
|
||||
</StyledLabelAndIconContainer>
|
||||
{isFieldInEditMode && !displayModeContentOnly ? (
|
||||
<EditableFieldEditMode onSubmit={onSubmit} onCancel={onCancel}>
|
||||
{editModeContent}
|
||||
</EditableFieldEditMode>
|
||||
) : (
|
||||
<EditableFieldDisplayMode
|
||||
disableHoverEffect={disableHoverEffect}
|
||||
disableClick={useEditButton}
|
||||
onClick={handleDisplayModeClick}
|
||||
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
|
||||
>
|
||||
{displayModeContent}
|
||||
</EditableFieldDisplayMode>
|
||||
)}
|
||||
<StyledValueContainer>
|
||||
{isFieldInEditMode && !displayModeContentOnly ? (
|
||||
<EditableFieldEditMode onSubmit={onSubmit} onCancel={onCancel}>
|
||||
{editModeContent}
|
||||
</EditableFieldEditMode>
|
||||
) : (
|
||||
<EditableFieldDisplayMode
|
||||
disableHoverEffect={disableHoverEffect}
|
||||
disableClick={useEditButton}
|
||||
onClick={handleDisplayModeClick}
|
||||
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
|
||||
isDisplayModeFixHeight={isDisplayModeFixHeight}
|
||||
>
|
||||
{displayModeContent}
|
||||
</EditableFieldDisplayMode>
|
||||
)}
|
||||
</StyledValueContainer>
|
||||
{showEditButton && (
|
||||
<motion.div
|
||||
<StyledEditButtonContainer
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.1 }}
|
||||
whileHover={{ scale: 1.04 }}
|
||||
>
|
||||
<EditableFieldEditButton />
|
||||
</motion.div>
|
||||
</StyledEditButtonContainer>
|
||||
)}
|
||||
</EditableFieldBaseContainer>
|
||||
);
|
||||
|
@ -4,15 +4,18 @@ import styled from '@emotion/styled';
|
||||
export const EditableFieldNormalModeOuterContainer = styled.div<
|
||||
Pick<
|
||||
OwnProps,
|
||||
'disableClick' | 'isDisplayModeContentEmpty' | 'disableHoverEffect'
|
||||
| 'disableClick'
|
||||
| 'isDisplayModeContentEmpty'
|
||||
| 'disableHoverEffect'
|
||||
| 'isDisplayModeFixHeight'
|
||||
>
|
||||
>`
|
||||
align-items: center;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
height: 16px;
|
||||
height: ${({ isDisplayModeFixHeight }) =>
|
||||
isDisplayModeFixHeight ? '16px' : 'auto'};
|
||||
min-height: 16px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
@ -67,6 +70,7 @@ type OwnProps = {
|
||||
onClick?: () => void;
|
||||
isDisplayModeContentEmpty?: boolean;
|
||||
disableHoverEffect?: boolean;
|
||||
isDisplayModeFixHeight?: boolean;
|
||||
};
|
||||
|
||||
export function EditableFieldDisplayMode({
|
||||
@ -75,6 +79,7 @@ export function EditableFieldDisplayMode({
|
||||
onClick,
|
||||
isDisplayModeContentEmpty,
|
||||
disableHoverEffect,
|
||||
isDisplayModeFixHeight,
|
||||
}: React.PropsWithChildren<OwnProps>) {
|
||||
return (
|
||||
<EditableFieldNormalModeOuterContainer
|
||||
@ -82,6 +87,7 @@ export function EditableFieldDisplayMode({
|
||||
disableClick={disableClick}
|
||||
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
|
||||
disableHoverEffect={disableHoverEffect}
|
||||
isDisplayModeFixHeight={isDisplayModeFixHeight}
|
||||
>
|
||||
<EditableFieldNormalModeInnerContainer>
|
||||
{children}
|
||||
|
@ -7,10 +7,12 @@ export const EditableFieldEditModeContainer = styled.div<OwnProps>`
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
height: 24px;
|
||||
|
||||
margin-left: -${({ theme }) => theme.spacing(1)};
|
||||
position: relative;
|
||||
|
||||
width: inherit;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
`;
|
||||
|
||||
|
@ -7,7 +7,7 @@ const StyledPropertyBoxContainer = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
|
@ -10,11 +10,12 @@ import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
|
||||
|
||||
type OwnProps = {
|
||||
icon?: React.ReactNode;
|
||||
label?: string;
|
||||
value: string | null | undefined;
|
||||
onSubmit?: (newValue: string) => void;
|
||||
};
|
||||
|
||||
export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
|
||||
export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
@ -47,6 +48,7 @@ export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
iconLabel={icon}
|
||||
label={label}
|
||||
editModeContent={
|
||||
<EditableFieldEditModeDate
|
||||
value={internalValue || new Date().toISOString()}
|
||||
|
@ -14,12 +14,14 @@ type OwnProps = {
|
||||
export const StyledDoubleTextContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
& > input:last-child {
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
const StyledNameInput = styled(StyledInput)`
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: auto;
|
||||
`;
|
||||
|
||||
export function InplaceInputDoubleText({
|
||||
@ -31,7 +33,8 @@ export function InplaceInputDoubleText({
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<StyledDoubleTextContainer>
|
||||
<StyledInput
|
||||
<StyledNameInput
|
||||
size={firstValue.length}
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
value={firstValue}
|
||||
@ -39,7 +42,8 @@ export function InplaceInputDoubleText({
|
||||
onChange(event.target.value, secondValue);
|
||||
}}
|
||||
/>
|
||||
<StyledInput
|
||||
<StyledNameInput
|
||||
size={secondValue.length}
|
||||
autoComplete="off"
|
||||
placeholder={secondValuePlaceholder}
|
||||
value={secondValue}
|
||||
|
@ -43,9 +43,12 @@ const StyledDate = styled.div`
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTooltip = styled(Tooltip)`
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useRef } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
@ -6,6 +7,7 @@ import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||
|
||||
@ -25,6 +27,7 @@ export function MultipleEntitySelect<
|
||||
>({
|
||||
entities,
|
||||
onChange,
|
||||
onSubmit,
|
||||
onSearchFilterChange,
|
||||
searchFilter,
|
||||
value,
|
||||
@ -33,6 +36,8 @@ export function MultipleEntitySelect<
|
||||
searchFilter: string;
|
||||
onSearchFilterChange: (newSearchFilter: string) => void;
|
||||
onChange: (value: Record<string, boolean>) => void;
|
||||
onCancel?: () => void;
|
||||
onSubmit?: () => void;
|
||||
value: Record<string, boolean>;
|
||||
}) {
|
||||
const debouncedSetSearchFilter = debounce(onSearchFilterChange, 100, {
|
||||
@ -53,8 +58,21 @@ export function MultipleEntitySelect<
|
||||
isNonEmptyString(entity.name),
|
||||
);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: (event) => {
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
onSubmit?.();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu ref={containerRef}>
|
||||
<DropdownMenuSearch
|
||||
value={searchFilter}
|
||||
onChange={handleFilterChange}
|
||||
|
@ -33,6 +33,13 @@ export function GenericEditableDoubleTextChipCellDisplayMode({
|
||||
}),
|
||||
);
|
||||
|
||||
const [avatarUrlValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.avatarUrlFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const displayName = `${firstValue} ${secondValue}`;
|
||||
|
||||
switch (viewField.metadata.entityType) {
|
||||
@ -40,7 +47,13 @@ export function GenericEditableDoubleTextChipCellDisplayMode({
|
||||
return <CompanyChip id={currentRowEntityId ?? ''} name={displayName} />;
|
||||
}
|
||||
case Entity.Person: {
|
||||
return <PersonChip id={currentRowEntityId ?? ''} name={displayName} />;
|
||||
return (
|
||||
<PersonChip
|
||||
id={currentRowEntityId ?? ''}
|
||||
name={displayName}
|
||||
pictureUrl={avatarUrlValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
|
@ -45,6 +45,7 @@ export function GenericEditableRelationCellDisplayMode({
|
||||
<UserChip
|
||||
id={fieldValue?.id ?? ''}
|
||||
name={fieldValue?.displayName ?? ''}
|
||||
pictureUrl={fieldValue?.avatarUrl ?? ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ export type ViewFieldDoubleTextChipMetadata = {
|
||||
firstValuePlaceholder: string;
|
||||
secondValueFieldName: string;
|
||||
secondValuePlaceholder: string;
|
||||
avatarUrlFieldName: string;
|
||||
entityType: Entity;
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { graphql } from 'msw';
|
||||
|
||||
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '@/activities/queries';
|
||||
import { CREATE_ACTIVITY_WITH_COMMENT } from '@/activities/queries/create';
|
||||
import { GET_COMPANY } from '@/companies/queries';
|
||||
import { GET_COMPANY, UPDATE_ONE_COMPANY } from '@/companies/queries';
|
||||
import {
|
||||
PageDecorator,
|
||||
type PageDecoratorArgs,
|
||||
@ -95,6 +95,16 @@ export const EditNote: Story = {
|
||||
}),
|
||||
);
|
||||
}),
|
||||
graphql.mutation(
|
||||
getOperationName(UPDATE_ONE_COMPANY) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
updateOneCompany: [mockedCompaniesData[0]],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import { GET_COMPANIES } from '@/companies/queries';
|
||||
import { GET_PEOPLE, GET_PERSON, UPDATE_ONE_PERSON } from '@/people/queries';
|
||||
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
|
||||
import {
|
||||
SEARCH_ACTIVITY_QUERY,
|
||||
SEARCH_COMPANY_QUERY,
|
||||
SEARCH_PEOPLE_QUERY,
|
||||
SEARCH_USER_QUERY,
|
||||
@ -16,11 +17,13 @@ import {
|
||||
GetCompaniesQuery,
|
||||
GetPeopleQuery,
|
||||
GetPersonQuery,
|
||||
SearchActivityQuery,
|
||||
SearchCompanyQuery,
|
||||
SearchPeopleQuery,
|
||||
SearchUserQuery,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { mockedActivities } from './mock-data/activities';
|
||||
import { mockedCompaniesData } from './mock-data/companies';
|
||||
import { mockedPeopleData } from './mock-data/people';
|
||||
import { mockedPipelineProgressData } from './mock-data/pipeline-progress';
|
||||
@ -103,6 +106,26 @@ export const graphqlMocks = [
|
||||
}),
|
||||
);
|
||||
}),
|
||||
graphql.query(
|
||||
getOperationName(SEARCH_ACTIVITY_QUERY) ?? '',
|
||||
(req, res, ctx) => {
|
||||
const returnedMockedData = filterAndSortData<
|
||||
SearchActivityQuery['searchResults'][0]
|
||||
>(
|
||||
mockedActivities,
|
||||
req.variables.where,
|
||||
Array.isArray(req.variables.orderBy)
|
||||
? req.variables.orderBy
|
||||
: [req.variables.orderBy],
|
||||
req.variables.limit,
|
||||
);
|
||||
return res(
|
||||
ctx.data({
|
||||
searchResults: returnedMockedData,
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
|
@ -16,6 +16,8 @@ type MockedActivity = Pick<
|
||||
| 'body'
|
||||
| 'title'
|
||||
| 'authorId'
|
||||
| 'dueAt'
|
||||
| 'completedAt'
|
||||
> & {
|
||||
author: {
|
||||
__typename?: 'User' | undefined;
|
||||
@ -24,6 +26,13 @@ type MockedActivity = Pick<
|
||||
lastName: string;
|
||||
displayName: string;
|
||||
};
|
||||
assignee: {
|
||||
__typename?: 'User' | undefined;
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
displayName: string;
|
||||
};
|
||||
comments: Array<Pick<Comment, 'body'>>;
|
||||
activityTargets: Array<
|
||||
Pick<
|
||||
@ -47,12 +56,20 @@ export const mockedActivities: Array<MockedActivity> = [
|
||||
title: 'My very first note',
|
||||
type: ActivityType.Note,
|
||||
body: null,
|
||||
dueAt: null,
|
||||
completedAt: null,
|
||||
author: {
|
||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
displayName: 'Charles Test',
|
||||
},
|
||||
assignee: {
|
||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
displayName: 'Charles Test',
|
||||
},
|
||||
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
comments: [],
|
||||
activityTargets: [
|
||||
@ -94,12 +111,20 @@ export const mockedActivities: Array<MockedActivity> = [
|
||||
title: 'Another note',
|
||||
body: null,
|
||||
type: ActivityType.Note,
|
||||
completedAt: null,
|
||||
dueAt: null,
|
||||
author: {
|
||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
displayName: 'Charles Test',
|
||||
},
|
||||
assignee: {
|
||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
displayName: 'Charles Test',
|
||||
},
|
||||
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
comments: [],
|
||||
activityTargets: [
|
||||
|
@ -18,7 +18,7 @@ describe('beautifyExactDate', () => {
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(DEFAULT_DATE_LOCALE)
|
||||
.toFormat('DD · TT');
|
||||
.toFormat('DD · T');
|
||||
|
||||
const result = beautifyExactDate(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -33,7 +33,7 @@ export function beautifyExactDate(dateToBeautify: Date | string | number) {
|
||||
try {
|
||||
const parsedDate = parseDate(dateToBeautify);
|
||||
|
||||
return parsedDate.toFormat('DD · TT');
|
||||
return parsedDate.toFormat('DD · T');
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
|
Loading…
Reference in New Issue
Block a user