Files
twenty/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx
T
Lucas Bordeau 5f3253d5a5 Fix issues with one to many activity targets (#15656)
This PR fixes issues with one to many activity target bugs described
here :
https://github.com/twentyhq/twenty/issues/14280#issuecomment-3490170714

It also improves the request that fetches notes and tasks in index
pages, to only fetch the notes and tasks identifier fields, thus
reducing the amount of network load.

Fixes https://github.com/twentyhq/twenty/issues/14280
2025-11-11 17:19:20 +01:00

156 lines
5.1 KiB
TypeScript

import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId';
import { type Task } from '@/activities/types/Task';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
import { FieldContextProvider } from '@/object-record/record-field/ui/components/FieldContextProvider';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { Checkbox, CheckboxShape } from 'twenty-ui/input';
import { useCompleteTask } from '../hooks/useCompleteTask';
const StyledTaskBody = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
max-width: calc(80% - ${({ theme }) => theme.spacing(2)});
text-overflow: ellipsis;
overflow: hidden;
padding-bottom: ${({ theme }) => theme.spacing(0.25)};
`;
const StyledTaskTitle = styled.div<{
completed: boolean;
}>`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
padding: 0 ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(0.25)};
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
align-items: center;
`;
const StyledDueDate = styled.div<{
isPast: boolean;
}>`
align-items: center;
color: ${({ theme, isPast }) =>
isPast ? theme.font.color.danger : theme.font.color.secondary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(1)};
white-space: nowrap;
`;
const StyledRightSideContainer = styled.div`
align-items: center;
display: inline-flex;
max-width: 50%;
`;
const StyledActivityTargetsContainer = styled.div`
overflow: clip;
width: 100%;
`;
const StyledPlaceholder = styled.div`
color: ${({ theme }) => theme.font.color.light};
`;
const StyledLeftSideContainer = styled.div`
align-items: center;
display: inline-flex;
display: flex;
flex: 1;
overflow: hidden;
`;
const StyledCheckboxContainer = styled.div`
display: flex;
`;
export const TaskRow = ({ task }: { task: Task }) => {
const theme = useTheme();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const body = getActivitySummary(task?.bodyV2?.blocknote ?? null);
const { completeTask } = useCompleteTask(task);
const baseComponentInstanceId = `task-row-targets-${task.id}`;
const componentInstanceId = useActivityTargetsComponentInstanceId(
baseComponentInstanceId,
);
return (
<ActivityRow
onClick={() => {
openRecordInCommandMenu({
recordId: task.id,
objectNameSingular: CoreObjectNameSingular.Task,
});
}}
>
<StyledLeftSideContainer>
<StyledCheckboxContainer
onClick={(e) => {
e.stopPropagation();
}}
>
<Checkbox
checked={task.status === 'DONE'}
shape={CheckboxShape.Rounded}
onCheckedChange={completeTask}
/>
</StyledCheckboxContainer>
<StyledTaskTitle completed={task.status === 'DONE'}>
{task.title || <StyledPlaceholder>Task title</StyledPlaceholder>}
</StyledTaskTitle>
<StyledTaskBody>
<OverflowingTextWithTooltip text={body} />
</StyledTaskBody>
</StyledLeftSideContainer>
<StyledRightSideContainer>
{task.dueAt && (
<StyledDueDate
isPast={hasDatePassed(task.dueAt) && task.status === 'TODO'}
>
<IconCalendar size={theme.icon.size.md} />
{beautifyExactDate(task.dueAt)}
</StyledDueDate>
)}
{
<StyledActivityTargetsContainer>
<FieldContextProvider
objectNameSingular={CoreObjectNameSingular.Task}
objectRecordId={task.id}
fieldMetadataName="taskTargets"
fieldPosition={0}
>
<StopPropagationContainer>
<ActivityTargetsInlineCell
activityObjectNameSingular={CoreObjectNameSingular.Task}
activityRecordId={task.id}
showLabel={false}
maxWidth={200}
componentInstanceId={componentInstanceId}
/>
</StopPropagationContainer>
</FieldContextProvider>
</StyledActivityTargetsContainer>
}
</StyledRightSideContainer>
</ActivityRow>
);
};