Skip to content

Commit

Permalink
feat: goals lazy pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
asabotovich committed Sep 12, 2024
1 parent 568bfc3 commit 5e08603
Show file tree
Hide file tree
Showing 31 changed files with 589 additions and 691 deletions.
12 changes: 12 additions & 0 deletions cypress/fixtures/langs.json
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,12 @@
"en": "shows this instruction"
}
},
"Loader": {
"Loading ...": {
"ru": "",
"en": ""
}
},
"LoadMoreButton": {
"Load more...": {
"ru": "Показать ещё...",
Expand Down Expand Up @@ -1055,6 +1061,12 @@
"en": "Create"
}
},
"ProjectGoalList": {
"Loading ...": {
"ru": "Загрузка...",
"en": "Loading ..."
}
},
"ProjectListItemConnected": {
"Loading ...": {
"ru": "Загрузка ...",
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@sentry/nextjs": "7.99.0",
"@tanstack/react-query": "4.29.5",
"@tanstack/react-query-devtools": "4.29.6",
"@taskany/bricks": "5.46.1",
"@taskany/bricks": "5.48.0",
"@taskany/colors": "1.13.0",
"@taskany/icons": "2.0.7",
"@tippyjs/react": "4.2.6",
Expand Down
126 changes: 29 additions & 97 deletions src/components/DashboardPage/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { nullable } from '@taskany/bricks';
import { ListView, TreeViewElement } from '@taskany/bricks/harmony';
import { ListView } from '@taskany/bricks/harmony';

import { refreshInterval } from '../../utils/config';
import { dashboardLoadMore } from '../../utils/domObjects';
Expand All @@ -14,89 +14,53 @@ import { Page } from '../Page/Page';
import { useGoalPreview } from '../GoalPreview/GoalPreviewProvider';
import { useFMPMetric } from '../../utils/telemetry';
import { LoadMoreButton } from '../LoadMoreButton/LoadMoreButton';
import { ProjectListItemCollapsable } from '../ProjectListItemCollapsable/ProjectListItemCollapsable';
import { GoalTableList, mapToRenderProps } from '../GoalTableList/GoalTableList';
import { ProjectListItemConnected } from '../ProjectListItemConnected/ProjectListItemConnected';
import { PresetModals } from '../PresetModals';
import { FiltersPanel } from '../FiltersPanel/FiltersPanel';
import { Kanban, buildKanban } from '../Kanban/Kanban';
import { NoGoalsText } from '../NoGoalsText/NoGoalsText';
import { safeUserData } from '../../utils/getUserName';
import { routes } from '../../hooks/router';

import { tr } from './DashboardPage.i18n';

export const DashboardPage = ({ user, ssrTime, defaultPresetFallback }: ExternalPageProps) => {
const utils = trpc.useContext();

const { preset } = useFiltersPreset({ defaultPresetFallback });

const { currentPreset, queryState, view } = useUrlFilterParams({
const { currentPreset, queryState } = useUrlFilterParams({
preset,
});

const { data, isLoading, isFetching, fetchNextPage, hasNextPage } =
trpc.v2.project.userProjectsWithGoals.useInfiniteQuery(
{
goalsQuery: {
...queryState,
limit: view === 'kanban' ? 50 : 30,
},
},
{
getNextPageParam: ({ pagination }) => pagination.offset,
keepPreviousData: true,
staleTime: refreshInterval,
const { data, isFetching, fetchNextPage, hasNextPage } = trpc.v2.project.getUserDashboardProjects.useInfiniteQuery(
{
goalsQuery: {
...queryState,
limit: 10,
},
);
},
{
getNextPageParam: ({ pagination }) => pagination.offset,
keepPreviousData: true,
staleTime: refreshInterval,
},
);

const pages = useMemo(() => data?.pages || [], [data?.pages]);

const [groupsOnScreen, canbansByProject, goalsCount, totalGoalsCount] = useMemo(() => {
const [groupsOnScreen, goalsCount, totalGoalsCount] = useMemo(() => {
const groups = pages?.[0]?.groups;

const gr = pages.reduce<typeof groups>((acc, cur) => {
acc.push(...cur.groups);
return acc;
}, []);

const canbans = gr.reduce<Record<string, React.ComponentProps<typeof Kanban>['value']>>((acum, project) => {
acum[project.id] = buildKanban(project.goals ?? [], (goal) => ({
...goal,
shortId: goal._shortId,
id: goal.id,
commentsCount: goal._count.comments ?? 0,
progress: goal._achivedCriteriaWeight,
}));

return acum;
}, {});

return [
gr,
canbans,
gr.reduce((acc, group) => acc + group._count.goals, 0),
pages.reduce((acc, { totalGoalsCount = 0 }) => acc + Number(totalGoalsCount), 0),
];
}, [pages]);

useFMPMetric(!!data);

const { setPreview, on } = useGoalPreview();

useEffect(() => {
const unsubUpdate = on('on:goal:update', () => {
utils.project.getUserProjectsWithGoals.invalidate();
});

const unsubDelete = on('on:goal:delete', () => {
utils.project.getUserProjectsWithGoals.invalidate();
});

return () => {
unsubUpdate();
unsubDelete();
};
}, [on, utils.project.getUserProjectsWithGoals]);
const { setPreview } = useGoalPreview();

const handleItemEnter = useCallback(
(goal: NonNullable<GoalByIdReturnType>) => {
Expand All @@ -120,55 +84,23 @@ export const DashboardPage = ({ user, ssrTime, defaultPresetFallback }: External
total={totalGoalsCount}
counter={goalsCount}
filterPreset={preset}
loading={isLoading}
enableLayoutToggle
enableHideProjectToggle
/>
}
>
<ListView onKeyboardClick={handleItemEnter}>
{groupsOnScreen?.map(({ goals, ...project }) => {
const kanban = canbansByProject[project.id];

const children = nullable(
view === 'kanban',
() => <Kanban value={kanban} filterPreset={preset} />,
nullable(goals, (g) => (
<TreeViewElement>
<GoalTableList
goals={mapToRenderProps(g, (goal) => ({
...goal,
shortId: goal._shortId,
commentsCount: goal._count.comments,
owner: safeUserData(goal.owner),
participants: goal.participants?.map(safeUserData),
achievedCriteriaWeight: goal._achivedCriteriaWeight,
partnershipProjects: goal.partnershipProjects,
isInPartnerProject: project.id !== goal.projectId,
}))}
/>
</TreeViewElement>
)),
);

return (
<ProjectListItemCollapsable
href={routes.project(project.id, view ? `view=${view}` : undefined)}
key={project.id}
interactive
visible
project={project}
goals={children}
actionButtonView="icons"
>
<TreeViewElement>
{nullable(!goals?.length, () => (
<NoGoalsText />
))}
</TreeViewElement>
</ProjectListItemCollapsable>
);
})}
{groupsOnScreen?.map(({ ...project }, i) => (
<ProjectListItemConnected
firstLevel
key={project.id}
project={project}
filterPreset={preset}
partnershipProject={project.partnerProjectIds}
actionButtonView="icons"
visible={i === 0}
/>
))}
</ListView>

{nullable(hasNextPage, () => (
Expand Down
1 change: 0 additions & 1 deletion src/components/FiltersPanel/FiltersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export const FiltersPanel: FC<{
title: string;
total?: number;
counter?: number;
loading?: boolean;
filterPreset?: FilterById;
enableViewToggle?: boolean;
enableLayoutToggle?: boolean;
Expand Down
1 change: 0 additions & 1 deletion src/components/GoalCreateForm/GoalCreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ const GoalCreateForm: React.FC<GoalCreateFormProps> = ({

utils.project.getAll.invalidate();
utils.goal.getBatch.invalidate();
utils.project.getUserProjectsWithGoals.invalidate();
utils.v2.project.userProjects.invalidate();

if (form.parent && form.mode === 'default') {
Expand Down
1 change: 0 additions & 1 deletion src/components/GoalEditForm/GoalEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ const GoalEditForm: React.FC<GoalEditFormProps> = ({ goal, onSubmit }) => {

utils.project.getAll.invalidate();
utils.goal.getBatch.invalidate();
utils.project.getUserProjectsWithGoals.invalidate();

if (!updatedGoal) {
return;
Expand Down
Loading

0 comments on commit 5e08603

Please sign in to comment.