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