Files
twenty_crm/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx
Avinash Bhardwaj 1ff31a90f4 sort task groups reverse alphabetically by their status (#6886)
This PR Solves #6830 

## Issue Summary
The tasks are grouped by their respective statuses and displayed on the
ui. The grouping is performed by `lodash.groupBy` which doesn't maintain
explicit ordering of the keys.

## Fix
Sort the tasks groups array by their status on the basis of
reverse-alphabetical order before generating task component for each
task data.

#### Why reverse alphabetical?
It implicitly sorts the statuses as per the order `TODO` ->
`IN_PROGRESS` -> `DONE`
Caveats:
1. Changing the name of one or more status might result in a different
unwanted order.
2. `null` is unhandled, although the original code doesn't allow for
nulls as status while displaying

### Alternative Fix
Maintain an explicit ordering of the statuses and sort the tasks
accordingly.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2024-09-09 15:56:52 +02:00

155 lines
4.6 KiB
TypeScript

import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox';
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import { useCompleteTask } from '../hooks/useCompleteTask';
const StyledContainer = styled.div`
align-items: center;
justify-content: space-between;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
cursor: pointer;
display: inline-flex;
height: ${({ theme }) => theme.spacing(12)};
min-width: calc(100% - ${({ theme }) => theme.spacing(8)});
max-width: calc(100% - ${({ theme }) => theme.spacing(8)});
padding: 0 ${({ theme }) => theme.spacing(4)};
overflow: hidden;
&:last-child {
border-bottom: 0;
}
`;
const StyledTaskBody = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
max-width: 100%;
flex: 1;
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(2)};
white-space: nowrap;
`;
const StyledRightSideContainer = styled.div`
display: flex;
`;
const StyledPlaceholder = styled.div`
color: ${({ theme }) => theme.font.color.light};
`;
const StyledLeftSideContainer = styled.div`
align-items: center;
display: flex;
flex: 1;
overflow: hidden;
`;
const StyledCheckboxContainer = styled.div`
display: flex;
`;
export const TaskRow = ({ task }: { task: Task }) => {
const theme = useTheme();
const openActivityRightDrawer = useOpenActivityRightDrawer({
objectNameSingular: CoreObjectNameSingular.Task,
});
const body = getActivitySummary(task.body);
const { completeTask } = useCompleteTask(task);
const { FieldContextProvider: TaskTargetsContextProvider } = useFieldContext({
objectNameSingular: CoreObjectNameSingular.Task,
objectRecordId: task.id,
fieldMetadataName: 'taskTargets',
fieldPosition: 0,
});
return (
<StyledContainer
onClick={() => {
openActivityRightDrawer(task.id);
}}
>
<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>
{TaskTargetsContextProvider && (
<TaskTargetsContextProvider>
<ActivityTargetsInlineCell
activityObjectNameSingular={CoreObjectNameSingular.Task}
activity={task}
showLabel={false}
maxWidth={200}
readonly
/>
</TaskTargetsContextProvider>
)}
<StyledDueDate
isPast={
!!task.dueAt && hasDatePassed(task.dueAt) && task.status === 'TODO'
}
>
<IconCalendar size={theme.icon.size.md} />
{task.dueAt && beautifyExactDate(task.dueAt)}
</StyledDueDate>
</StyledRightSideContainer>
</StyledContainer>
);
};