Fix note linked text in timeline view (in dark mode) (#6944)
This PR Fixes https://github.com/twentyhq/twenty/issues/6942 Other improvements : - Fetch activities (note and task) title only when loading timeline, so we don't always have a clickable title. - Fixed IconButton width regression. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -3,7 +3,8 @@ import { useContext } from 'react';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext';
|
import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext';
|
||||||
import { useLinkedObject } from '@/activities/timelineActivities/hooks/useLinkedObject';
|
|
||||||
|
import { useLinkedObjectObjectMetadataItem } from '@/activities/timelineActivities/hooks/useLinkedObjectObjectMetadataItem';
|
||||||
import { EventIconDynamicComponent } from '@/activities/timelineActivities/rows/components/EventIconDynamicComponent';
|
import { EventIconDynamicComponent } from '@/activities/timelineActivities/rows/components/EventIconDynamicComponent';
|
||||||
import { EventRowDynamicComponent } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
|
import { EventRowDynamicComponent } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
|
||||||
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
||||||
@ -100,7 +101,7 @@ export const EventRow = ({
|
|||||||
|
|
||||||
const { labelIdentifierValue } = useContext(TimelineActivityContext);
|
const { labelIdentifierValue } = useContext(TimelineActivityContext);
|
||||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt);
|
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt);
|
||||||
const linkedObjectMetadataItem = useLinkedObject(
|
const linkedObjectMetadataItem = useLinkedObjectObjectMetadataItem(
|
||||||
event.linkedObjectMetadataId,
|
event.linkedObjectMetadataId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export const TimelineActivities = ({
|
|||||||
const isTimelineActivitiesEmpty =
|
const isTimelineActivitiesEmpty =
|
||||||
!timelineActivities || timelineActivities.length === 0;
|
!timelineActivities || timelineActivities.length === 0;
|
||||||
|
|
||||||
if (loading && isTimelineActivitiesEmpty) {
|
if (loading) {
|
||||||
return <SkeletonLoader withSubSections />;
|
return <SkeletonLoader withSubSections />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
export const useLinkedObject = (id: string) => {
|
export const useLinkedObjectObjectMetadataItem = (id: string) => {
|
||||||
const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue(
|
const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue(
|
||||||
objectMetadataItemsState,
|
objectMetadataItemsState,
|
||||||
);
|
);
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
|
||||||
|
import { isNonEmptyArray } from '@sniptt/guards';
|
||||||
|
|
||||||
|
export const useLinkedObjectsTitle = (linkedObjectIds: string[]) => {
|
||||||
|
const { loading } = useCombinedFindManyRecords({
|
||||||
|
skip: !isNonEmptyArray(linkedObjectIds),
|
||||||
|
operationSignatures: [
|
||||||
|
{
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Task,
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
id: {
|
||||||
|
in: linkedObjectIds ?? [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Note,
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
id: {
|
||||||
|
in: linkedObjectIds ?? [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useLinkedObjectsTitle } from '@/activities/timelineActivities/hooks/useLinkedObjectsTitle';
|
||||||
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
|
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
|
||||||
@ -19,10 +20,10 @@ export const useTimelineActivities = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
records: TimelineActivities,
|
records: timelineActivities,
|
||||||
loading,
|
loading: loadingTimelineActivities,
|
||||||
fetchMoreRecords,
|
fetchMoreRecords,
|
||||||
} = useFindManyRecords({
|
} = useFindManyRecords<TimelineActivity>({
|
||||||
objectNameSingular: CoreObjectNameSingular.TimelineActivity,
|
objectNameSingular: CoreObjectNameSingular.TimelineActivity,
|
||||||
filter: {
|
filter: {
|
||||||
[targetableObjectFieldIdName]: {
|
[targetableObjectFieldIdName]: {
|
||||||
@ -38,8 +39,17 @@ export const useTimelineActivities = (
|
|||||||
fetchPolicy: 'cache-and-network',
|
fetchPolicy: 'cache-and-network',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const activityIds = timelineActivities
|
||||||
|
.filter((timelineActivity) => timelineActivity.name.match(/note|task/i))
|
||||||
|
.map((timelineActivity) => timelineActivity.linkedRecordId);
|
||||||
|
|
||||||
|
const { loading: loadingLinkedObjectsTitle } =
|
||||||
|
useLinkedObjectsTitle(activityIds);
|
||||||
|
|
||||||
|
const loading = loadingTimelineActivities || loadingLinkedObjectsTitle;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timelineActivities: TimelineActivities as TimelineActivity[],
|
timelineActivities,
|
||||||
loading,
|
loading,
|
||||||
fetchMoreRecords,
|
fetchMoreRecords,
|
||||||
};
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import {
|
import {
|
||||||
@ -8,15 +7,21 @@ import {
|
|||||||
StyledEventRowItemColumn,
|
StyledEventRowItemColumn,
|
||||||
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
|
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
type EventRowActivityProps = EventRowDynamicComponentProps;
|
type EventRowActivityProps = EventRowDynamicComponentProps;
|
||||||
|
|
||||||
const StyledLinkedActivity = styled.span`
|
const StyledLinkedActivity = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const StyledEventRowItemText = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
`;
|
||||||
|
|
||||||
export const EventRowActivity = ({
|
export const EventRowActivity = ({
|
||||||
event,
|
event,
|
||||||
authorFullName,
|
authorFullName,
|
||||||
@ -30,9 +35,21 @@ export const EventRowActivity = ({
|
|||||||
throw new Error('Could not find linked record id for event');
|
throw new Error('Could not find linked record id for event');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [activityInStore] = useRecoilState(
|
const getActivityFromCache = useGetRecordFromCache({
|
||||||
recordStoreFamilyState(event.linkedRecordId),
|
objectNameSingular,
|
||||||
);
|
recordGqlFields: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const activityInStore = getActivityFromCache(event.linkedRecordId);
|
||||||
|
|
||||||
|
const activityTitle = isNonEmptyString(activityInStore?.title)
|
||||||
|
? activityInStore?.title
|
||||||
|
: isNonEmptyString(event.linkedRecordCachedName)
|
||||||
|
? event.linkedRecordCachedName
|
||||||
|
: 'Untitled';
|
||||||
|
|
||||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -44,15 +61,11 @@ export const EventRowActivity = ({
|
|||||||
<StyledEventRowItemAction>
|
<StyledEventRowItemAction>
|
||||||
{`${eventAction} a related ${eventObject}`}
|
{`${eventAction} a related ${eventObject}`}
|
||||||
</StyledEventRowItemAction>
|
</StyledEventRowItemAction>
|
||||||
{activityInStore ? (
|
<StyledLinkedActivity
|
||||||
<StyledLinkedActivity
|
onClick={() => openActivityRightDrawer(event.linkedRecordId)}
|
||||||
onClick={() => openActivityRightDrawer(event.linkedRecordId)}
|
>
|
||||||
>
|
{activityTitle}
|
||||||
{event.linkedRecordCachedName}
|
</StyledLinkedActivity>
|
||||||
</StyledLinkedActivity>
|
|
||||||
) : (
|
|
||||||
<span>{event.linkedRecordCachedName}</span>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export type TimelineActivityLinkedObject = 'note' | 'task';
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
||||||
|
import { TimelineActivityLinkedObject } from '@/activities/timelineActivities/types/TimelineActivityLinkedObject';
|
||||||
|
|
||||||
|
export const filterTimelineActivityByLinkedObjectTypes =
|
||||||
|
(linkedObjectTypes: TimelineActivityLinkedObject[]) =>
|
||||||
|
(timelineActivity: TimelineActivity) => {
|
||||||
|
return linkedObjectTypes.some((linkedObjectType) => {
|
||||||
|
const linkedObjectPartInName = timelineActivity.name.split('.')[0];
|
||||||
|
|
||||||
|
return linkedObjectPartInName.includes(linkedObjectType);
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -11,13 +11,13 @@ export const useCombinedFindManyRecords = ({
|
|||||||
skip = false,
|
skip = false,
|
||||||
}: {
|
}: {
|
||||||
operationSignatures: RecordGqlOperationSignature[];
|
operationSignatures: RecordGqlOperationSignature[];
|
||||||
skip: boolean;
|
skip?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const findManyQuery = useGenerateCombinedFindManyRecordsQuery({
|
const findManyQuery = useGenerateCombinedFindManyRecordsQuery({
|
||||||
operationSignatures,
|
operationSignatures,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data } = useQuery<MultiObjectRecordQueryResult>(
|
const { data, loading } = useQuery<MultiObjectRecordQueryResult>(
|
||||||
findManyQuery ?? EMPTY_QUERY,
|
findManyQuery ?? EMPTY_QUERY,
|
||||||
{
|
{
|
||||||
skip,
|
skip,
|
||||||
@ -35,5 +35,6 @@ export const useCombinedFindManyRecords = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
result: resultWithoutConnection,
|
result: resultWithoutConnection,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
|
||||||
import { css, useTheme } from '@emotion/react';
|
import { css, useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import React from 'react';
|
||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
|
||||||
export type IconButtonSize = 'medium' | 'small';
|
export type IconButtonSize = 'medium' | 'small';
|
||||||
@ -233,7 +233,7 @@ const StyledButton = styled.button<
|
|||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
min-width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user