Add a notification for "tasks" in the navigation (#1489)
* Add a notification for "tasks" in the navigation Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> * Add a notification for "tasks" in the navigation Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> * Fix icon import in TaskNavMenuItem Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> * Use object destructuring Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> * Refactor according to review Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> * Rename dueTasks to dueTaskCount Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> * Complete Task notification display * Fix lint * Fix tests --------- Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: chiazokam <chiazokamecheta@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -68,7 +68,7 @@ export function TaskGroups({ entity, showAddButton }: OwnProps) {
|
||||
);
|
||||
|
||||
if (
|
||||
(activeTabId === 'to-do' &&
|
||||
(activeTabId !== 'done' &&
|
||||
todayOrPreviousTasks?.length === 0 &&
|
||||
upcomingTasks?.length === 0 &&
|
||||
unscheduledTasks?.length === 0) ||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { FilterOperand } from '@/ui/view-bar/types/FilterOperand';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/view-bar/utils/turnFilterIntoWhereClause';
|
||||
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
|
||||
import { parseDate } from '~/utils/date-utils';
|
||||
|
||||
export function useCurrentUserTaskCount() {
|
||||
const [currentUser] = useRecoilState(currentUserState);
|
||||
|
||||
const { data } = useGetActivitiesQuery({
|
||||
variables: {
|
||||
where: {
|
||||
type: { equals: ActivityType.Task },
|
||||
completedAt: { equals: null },
|
||||
...(currentUser
|
||||
? turnFilterIntoWhereClause({
|
||||
key: 'assigneeId',
|
||||
type: 'entity',
|
||||
value: currentUser.id,
|
||||
operand: FilterOperand.Is,
|
||||
displayValue: currentUser.displayName,
|
||||
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
|
||||
})
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const currentUserDueTaskCount = data?.findManyActivities.filter((task) => {
|
||||
if (!task.dueAt) {
|
||||
return false;
|
||||
}
|
||||
const dueDate = parseDate(task.dueAt).toJSDate();
|
||||
const today = DateTime.now().endOf('day').toJSDate();
|
||||
return dueDate <= today;
|
||||
}).length;
|
||||
|
||||
return {
|
||||
currentUserDueTaskCount,
|
||||
};
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState';
|
||||
import { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition';
|
||||
|
||||
import { TasksRecoilScopeContext } from '../../states/recoil-scope-contexts/TasksRecoilScopeContext';
|
||||
|
||||
export function useInitializeTasksFilters({
|
||||
availableFilters,
|
||||
}: {
|
||||
availableFilters: FilterDefinition[];
|
||||
}) {
|
||||
const [, setAvailableFilters] = useRecoilScopedState(
|
||||
availableFiltersScopedState,
|
||||
TasksRecoilScopeContext,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setAvailableFilters(availableFilters);
|
||||
}, [setAvailableFilters, availableFilters]);
|
||||
}
|
||||
@ -1,47 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { TasksRecoilScopeContext } from '@/activities/states/recoil-scope-contexts/TasksRecoilScopeContext';
|
||||
import { useInitializeTasksFilters } from '@/activities/tasks/hooks/useInitializeTasksFilters';
|
||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
|
||||
import { FilterOperand } from '@/ui/view-bar/types/FilterOperand';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/view-bar/utils/turnFilterIntoWhereClause';
|
||||
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
|
||||
import { tasksFilters } from '~/pages/tasks/tasks-filters';
|
||||
import { parseDate } from '~/utils/date-utils';
|
||||
|
||||
export function useTasks(entity?: ActivityTargetableEntity) {
|
||||
useInitializeTasksFilters({
|
||||
availableFilters: tasksFilters,
|
||||
});
|
||||
|
||||
const [filters, setFilters] = useRecoilScopedState(
|
||||
const [filters] = useRecoilScopedState(
|
||||
filtersScopedState,
|
||||
TasksRecoilScopeContext,
|
||||
);
|
||||
|
||||
// If there is no filter, we set the default filter to the current user
|
||||
const [currentUser] = useRecoilState(currentUserState);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser && !filters.length && !entity) {
|
||||
setFilters([
|
||||
{
|
||||
key: 'assigneeId',
|
||||
type: 'entity',
|
||||
value: currentUser.id,
|
||||
operand: FilterOperand.Is,
|
||||
displayValue: currentUser.displayName,
|
||||
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}, [currentUser, filters, setFilters, entity]);
|
||||
|
||||
const whereFilters = entity
|
||||
? {
|
||||
activityTargets: {
|
||||
@ -68,6 +40,7 @@ export function useTasks(entity?: ActivityTargetableEntity) {
|
||||
...whereFilters,
|
||||
},
|
||||
},
|
||||
skip: !entity && filters.length === 0,
|
||||
});
|
||||
|
||||
const { data: incompleteTaskData } = useGetActivitiesQuery({
|
||||
@ -78,6 +51,7 @@ export function useTasks(entity?: ActivityTargetableEntity) {
|
||||
...whereFilters,
|
||||
},
|
||||
},
|
||||
skip: !entity && filters.length === 0,
|
||||
});
|
||||
|
||||
const todayOrPreviousTasks = incompleteTaskData?.findManyActivities.filter(
|
||||
@ -111,9 +85,9 @@ export function useTasks(entity?: ActivityTargetableEntity) {
|
||||
const completedTasks = completeTasksData?.findManyActivities;
|
||||
|
||||
return {
|
||||
todayOrPreviousTasks,
|
||||
upcomingTasks,
|
||||
unscheduledTasks,
|
||||
completedTasks,
|
||||
todayOrPreviousTasks: todayOrPreviousTasks ?? [],
|
||||
upcomingTasks: upcomingTasks ?? [],
|
||||
unscheduledTasks: unscheduledTasks ?? [],
|
||||
completedTasks: completedTasks ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Company, User } from '../../../../generated/graphql';
|
||||
import { Company, Favorite, User } from '../../../../generated/graphql';
|
||||
|
||||
type MockedCompany = Pick<
|
||||
Company,
|
||||
@ -25,7 +25,7 @@ type MockedCompany = Pick<
|
||||
| 'firstName'
|
||||
| 'lastName'
|
||||
> | null;
|
||||
};
|
||||
} & { Favorite: Pick<Favorite, 'id'> | null };
|
||||
|
||||
export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
{
|
||||
@ -39,6 +39,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:08:54.724515+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 5000,
|
||||
Favorite: null,
|
||||
_activityCount: 0,
|
||||
accountOwner: {
|
||||
email: 'charles@test.com',
|
||||
@ -62,6 +63,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
address: 'Paris, France',
|
||||
employees: 800,
|
||||
Favorite: null,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
@ -77,6 +79,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:10:32.530184+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 8000,
|
||||
Favorite: null,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
@ -92,6 +95,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-03-21T06:30:25.39474+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 800,
|
||||
Favorite: null,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
@ -107,6 +111,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:13:29.712485+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 400,
|
||||
Favorite: null,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
|
||||
@ -17,6 +17,7 @@ type NavItemProps = {
|
||||
active?: boolean;
|
||||
danger?: boolean;
|
||||
soon?: boolean;
|
||||
count?: number;
|
||||
};
|
||||
|
||||
type StyledItemProps = {
|
||||
@ -82,6 +83,21 @@ const StyledSoonPill = styled.div`
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledItemCount = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.color.blue};
|
||||
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
||||
color: ${({ theme }) => theme.grayScale.gray0};
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
width: 16px;
|
||||
`;
|
||||
|
||||
function NavItem({
|
||||
label,
|
||||
Icon,
|
||||
@ -90,6 +106,7 @@ function NavItem({
|
||||
active,
|
||||
danger,
|
||||
soon,
|
||||
count,
|
||||
}: NavItemProps) {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
@ -120,6 +137,7 @@ function NavItem({
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
<StyledItemLabel>{label}</StyledItemLabel>
|
||||
{soon && <StyledSoonPill>Soon</StyledSoonPill>}
|
||||
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
||||
</StyledItem>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user