A few polish on tasks (#1023)

A few polishing on tasks
This commit is contained in:
Charles Bochet 2023-07-31 18:15:08 -07:00 committed by GitHub
parent 22ca00bb67
commit 8b8e4ac4a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 141 additions and 65 deletions

View File

@ -1,13 +1,11 @@
import { useCallback } from 'react';
import { Tooltip } from 'react-tooltip';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
import { useCompleteTask } from '@/tasks/hooks/useCompleteTask';
import { IconNotes } from '@/ui/icon';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import { Activity, User, useUpdateActivityMutation } from '~/generated/graphql';
import { Activity, User } from '~/generated/graphql';
import {
beautifyExactDate,
beautifyPastDateRelativeToNow,
@ -87,7 +85,7 @@ const StyledCardContent = styled.div`
align-self: stretch;
color: ${({ theme }) => theme.font.color.secondary};
margin-top: ${({ theme }) => theme.spacing(2)};
width: 100%;
width: calc(100% - ${({ theme }) => theme.spacing(4)});
`;
const StyledTooltip = styled(Tooltip)`
@ -132,22 +130,7 @@ export function TimelineActivity({ activity }: OwnProps) {
const body = JSON.parse(activity.body ?? '{}')[0]?.content[0]?.text;
const openActivityRightDrawer = useOpenActivityRightDrawer();
const [updateActivityMutation] = useUpdateActivityMutation();
const handleActivityCompletionChange = useCallback(
(value: boolean) => {
updateActivityMutation({
variables: {
where: { id: activity.id },
data: {
completedAt: value ? new Date().toISOString() : null,
},
},
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
});
},
[activity, updateActivityMutation],
);
const { completeTask } = useCompleteTask(activity);
return (
<>
@ -180,7 +163,7 @@ export function TimelineActivity({ activity }: OwnProps) {
title={activity.title ?? ''}
completed={!!activity.completedAt}
type={activity.type}
onCompletionChange={handleActivityCompletionChange}
onCompletionChange={completeTask}
/>
<StyledCardContent>
<OverflowingTextWithTooltip

View File

@ -3,11 +3,11 @@ import { useTasks } from '../hooks/useTasks';
import { TaskList } from './TaskList';
export function TaskGroups() {
const { todayTasks, otherTasks } = useTasks();
const { todayOrPreviousTasks, upcomingTasks } = useTasks();
return (
<>
<TaskList title="Today" tasks={todayTasks ?? []} />
<TaskList title="Others" tasks={otherTasks ?? []} />
<TaskList title="Today" tasks={todayOrPreviousTasks ?? []} />
<TaskList title="Upcoming" tasks={upcomingTasks ?? []} />
</>
);
}

View File

@ -8,9 +8,11 @@ import {
Checkbox,
CheckboxShape,
} from '@/ui/input/checkbox/components/Checkbox';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import { useGetCompaniesQuery, useGetPeopleQuery } from '~/generated/graphql';
import { beautifyExactDate } from '~/utils/date-utils';
import { useCompleteTask } from '../hooks/useCompleteTask';
import { TaskForList } from '../types/TaskForList';
const StyledContainer = styled.div`
@ -18,29 +20,44 @@ const StyledContainer = styled.div`
align-self: stretch;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
cursor: pointer;
display: flex;
display: inline-flex;
height: ${({ theme }) => theme.spacing(12)};
min-width: calc(100% - ${({ theme }) => theme.spacing(8)});
padding: 0 ${({ theme }) => theme.spacing(4)};
`;
const StyledSeparator = styled.div`
flex: 1;
const StyledTaskBody = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
flex-direction: row;
flex-grow: 1;
width: 1px;
`;
const StyledTaskTitle = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
padding: 0 ${({ theme }) => theme.spacing(2)};
`;
const StyledCommentIcon = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.light};
display: flex;
margin-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledDueDate = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledFieldsContainer = styled.div`
display: flex;
`;
export function TaskRow({ task }: { task: TaskForList }) {
const theme = useTheme();
const openActivityRightDrawer = useOpenActivityRightDrawer();
@ -71,6 +88,8 @@ export function TaskRow({ task }: { task: TaskForList }) {
},
},
});
const body = JSON.parse(task.body ?? '{}')[0]?.content[0]?.text;
const { completeTask } = useCompleteTask(task);
return (
<StyledContainer
@ -83,15 +102,22 @@ export function TaskRow({ task }: { task: TaskForList }) {
e.stopPropagation();
}}
>
<Checkbox checked={false} shape={CheckboxShape.Rounded} />
<Checkbox
checked={!!task.completedAt}
shape={CheckboxShape.Rounded}
onChange={completeTask}
/>
</div>
<StyledTaskTitle>{task.title}</StyledTaskTitle>
<StyledTaskTitle>{task.title ?? '(No title)'}</StyledTaskTitle>
<StyledTaskBody>
<OverflowingTextWithTooltip text={body} />
{task.comments && task.comments.length > 0 && (
<StyledCommentIcon>
<IconComment size={theme.icon.size.md} />
</StyledCommentIcon>
)}
<StyledSeparator />
</StyledTaskBody>
<StyledFieldsContainer>
<ActivityTargetChips
targetCompanies={targetCompanies}
targetPeople={targetPeople}
@ -100,6 +126,7 @@ export function TaskRow({ task }: { task: TaskForList }) {
<IconCalendar size={theme.icon.size.md} />
{task.dueAt && beautifyExactDate(task.dueAt)}
</StyledDueDate>
</StyledFieldsContainer>
</StyledContainer>
);
}

View File

@ -0,0 +1,35 @@
import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import {
GET_ACTIVITIES,
GET_ACTIVITIES_BY_TARGETS,
} from '@/activities/queries';
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
type Task = Pick<Activity, 'id'>;
export function useCompleteTask(task: Task) {
const [updateActivityMutation] = useUpdateActivityMutation();
const completeTask = useCallback(
(value: boolean) => {
updateActivityMutation({
variables: {
where: { id: task.id },
data: {
completedAt: value ? new Date().toISOString() : null,
},
},
refetchQueries: [
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
getOperationName(GET_ACTIVITIES) ?? '',
],
});
},
[task, updateActivityMutation],
);
return {
completeTask,
};
}

View File

@ -1,3 +1,5 @@
import { DateTime } from 'luxon';
import { activeTabIdScopedState } from '@/ui/tab/states/activeTabIdScopedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
@ -11,7 +13,16 @@ export function useTasks() {
TasksContext,
);
const { data, loading } = useGetActivitiesQuery({
const { data: completeTasksData } = useGetActivitiesQuery({
variables: {
where: {
type: { equals: ActivityType.Task },
completedAt: { not: { equals: null } },
},
},
});
const { data: incompleteTaskData } = useGetActivitiesQuery({
variables: {
where: {
type: { equals: ActivityType.Task },
@ -21,27 +32,28 @@ export function useTasks() {
},
});
const todayTasks = data?.findManyActivities.filter((task) => {
const data = activeTabId === 'done' ? completeTasksData : incompleteTaskData;
const todayOrPreviousTasks = data?.findManyActivities.filter((task) => {
if (!task.dueAt) {
return false;
}
const dueDate = parseDate(task.dueAt).toJSDate();
const today = new Date();
return dueDate.getDate() === today.getDate();
const today = DateTime.now().endOf('day').toJSDate();
return dueDate <= today;
});
const otherTasks = data?.findManyActivities.filter((task) => {
const upcomingTasks = data?.findManyActivities.filter((task) => {
if (!task.dueAt) {
return false;
}
const dueDate = parseDate(task.dueAt).toJSDate();
const today = new Date();
return dueDate.getDate() !== today.getDate();
const today = DateTime.now().endOf('day').toJSDate();
return dueDate > today;
});
return {
todayTasks,
otherTasks,
loading,
todayOrPreviousTasks,
upcomingTasks,
};
}

View File

@ -16,9 +16,10 @@ const StyledTab = styled.div<{ active?: boolean }>`
active ? theme.border.color.inverted : 'transparent'};
color: ${({ theme, active }) =>
active ? theme.font.color.primary : theme.font.color.secondary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
padding: ${({ theme }) => theme.spacing(2) + ' ' + theme.spacing(4)};
@ -38,7 +39,8 @@ export function Tab({
}: OwnProps) {
return (
<StyledTab onClick={onClick} active={active} className={className}>
{icon} {title}
{icon}
{title}
</StyledTab>
);
}

View File

@ -63,6 +63,7 @@ export function TableHeader<SortField>({
{viewName}
</>
}
displayBottomBorder={false}
rightComponents={[
<FilterDropdownButton
context={TableContext}

View File

@ -32,6 +32,7 @@ const common = {
},
},
spacing: (multiplicator: number) => `${multiplicator * 4}px`,
betweenSiblingsGap: `2px`,
table: {
horizontalCellMargin: '8px',
checkboxColumnWidth: '32px',

View File

@ -11,12 +11,12 @@ const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>`
font-size: inherit;
font-weight: inherit;
max-width: 100%;
overflow: hidden;
text-decoration: inherit;
text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
export function OverflowingTextWithTooltip({

View File

@ -5,6 +5,7 @@ type OwnProps = {
leftComponent?: ReactNode;
rightComponents?: ReactNode[];
bottomComponent?: ReactNode;
displayBottomBorder?: boolean;
};
const StyledContainer = styled.div`
@ -12,13 +13,16 @@ const StyledContainer = styled.div`
flex-direction: column;
`;
const StyledTableHeader = styled.div`
const StyledTopBar = styled.div<{ displayBottomBorder: boolean }>`
align-items: center;
border-bottom: ${({ displayBottomBorder, theme }) =>
displayBottomBorder ? `1px solid ${theme.border.color.light}` : 'none'};
box-sizing: border-box;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: 40px;
height: 39px;
justify-content: space-between;
padding-left: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(2)};
@ -31,20 +35,21 @@ const StyledLeftSection = styled.div`
const StyledRightSection = styled.div`
display: flex;
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: 2px;
gap: ${({ theme }) => theme.betweenSiblingsGap};
`;
export function TopBar({
leftComponent,
rightComponents,
bottomComponent,
displayBottomBorder = true,
}: OwnProps) {
return (
<StyledContainer>
<StyledTableHeader>
<StyledTopBar displayBottomBorder={displayBottomBorder}>
<StyledLeftSection>{leftComponent}</StyledLeftSection>
<StyledRightSection>{rightComponents}</StyledRightSection>
</StyledTableHeader>
</StyledTopBar>
{bottomComponent}
</StyledContainer>
);

View File

@ -17,6 +17,12 @@ const StyledTasksContainer = styled.div`
overflow: auto;
`;
const StyledTabListContainer = styled.div`
align-items: end;
display: flex;
height: 40px;
`;
export function Tasks() {
const theme = useTheme();
@ -41,7 +47,11 @@ export function Tasks() {
<StyledTasksContainer>
<RecoilScope SpecificContext={TasksContext}>
<TopBar
leftComponent={<TabList context={TasksContext} tabs={TASK_TABS} />}
leftComponent={
<StyledTabListContainer>
<TabList context={TasksContext} tabs={TASK_TABS} />
</StyledTabListContainer>
}
/>
<TaskGroups />
</RecoilScope>

View File

@ -62,7 +62,7 @@ export class EnvironmentService {
getFrontAuthCallbackUrl(): string {
return (
this.configService.get<string>('FRONT_AUTH_CALLBACK_URL') ??
this.getFrontBaseUrl() + '/auth/callback'
this.getFrontBaseUrl() + '/verify'
);
}