diff --git a/front/src/modules/activities/timeline/components/TimelineActivity.tsx b/front/src/modules/activities/timeline/components/TimelineActivity.tsx index d041ee38e8..9970b81be8 100644 --- a/front/src/modules/activities/timeline/components/TimelineActivity.tsx +++ b/front/src/modules/activities/timeline/components/TimelineActivity.tsx @@ -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} /> - - + + ); } diff --git a/front/src/modules/tasks/components/TaskRow.tsx b/front/src/modules/tasks/components/TaskRow.tsx index d56ed2fa7f..d3f0c450e7 100644 --- a/front/src/modules/tasks/components/TaskRow.tsx +++ b/front/src/modules/tasks/components/TaskRow.tsx @@ -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 ( - + - {task.title} - {task.comments && task.comments.length > 0 && ( - - - - )} - - - - - {task.dueAt && beautifyExactDate(task.dueAt)} - + {task.title ?? '(No title)'} + + + {task.comments && task.comments.length > 0 && ( + + + + )} + + + + + + {task.dueAt && beautifyExactDate(task.dueAt)} + + ); } diff --git a/front/src/modules/tasks/hooks/useCompleteTask.ts b/front/src/modules/tasks/hooks/useCompleteTask.ts new file mode 100644 index 0000000000..8ca77eae0d --- /dev/null +++ b/front/src/modules/tasks/hooks/useCompleteTask.ts @@ -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; + +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, + }; +} diff --git a/front/src/modules/tasks/hooks/useTasks.ts b/front/src/modules/tasks/hooks/useTasks.ts index 2dd2d5c890..ebcc8b4f46 100644 --- a/front/src/modules/tasks/hooks/useTasks.ts +++ b/front/src/modules/tasks/hooks/useTasks.ts @@ -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, }; } diff --git a/front/src/modules/ui/tab/components/Tab.tsx b/front/src/modules/ui/tab/components/Tab.tsx index 0c61cd5386..20c43cbeab 100644 --- a/front/src/modules/ui/tab/components/Tab.tsx +++ b/front/src/modules/ui/tab/components/Tab.tsx @@ -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 ( - {icon} {title} + {icon} + {title} ); } diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx index 9ce66ae079..f60d56d752 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -63,6 +63,7 @@ export function TableHeader({ {viewName} } + displayBottomBorder={false} rightComponents={[ `${multiplicator * 4}px`, + betweenSiblingsGap: `2px`, table: { horizontalCellMargin: '8px', checkboxColumnWidth: '32px', diff --git a/front/src/modules/ui/tooltip/OverflowingTextWithTooltip.tsx b/front/src/modules/ui/tooltip/OverflowingTextWithTooltip.tsx index b24d0632b3..b00808284f 100644 --- a/front/src/modules/ui/tooltip/OverflowingTextWithTooltip.tsx +++ b/front/src/modules/ui/tooltip/OverflowingTextWithTooltip.tsx @@ -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({ diff --git a/front/src/modules/ui/top-bar/TopBar.tsx b/front/src/modules/ui/top-bar/TopBar.tsx index 2652302c0e..ea124fde3e 100644 --- a/front/src/modules/ui/top-bar/TopBar.tsx +++ b/front/src/modules/ui/top-bar/TopBar.tsx @@ -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 ( - + {leftComponent} {rightComponents} - + {bottomComponent} ); diff --git a/front/src/pages/tasks/Tasks.tsx b/front/src/pages/tasks/Tasks.tsx index f3dd7b203d..96abbaf87b 100644 --- a/front/src/pages/tasks/Tasks.tsx +++ b/front/src/pages/tasks/Tasks.tsx @@ -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() { } + leftComponent={ + + + + } /> diff --git a/server/src/integrations/environment/environment.service.ts b/server/src/integrations/environment/environment.service.ts index b5dd52f0d5..8c49a2b0f6 100644 --- a/server/src/integrations/environment/environment.service.ts +++ b/server/src/integrations/environment/environment.service.ts @@ -62,7 +62,7 @@ export class EnvironmentService { getFrontAuthCallbackUrl(): string { return ( this.configService.get('FRONT_AUTH_CALLBACK_URL') ?? - this.getFrontBaseUrl() + '/auth/callback' + this.getFrontBaseUrl() + '/verify' ); }