Skip to content

Commit

Permalink
feat(GoalsPage): add new components
Browse files Browse the repository at this point in the history
  • Loading branch information
LamaEats committed Oct 13, 2023
1 parent 50be538 commit e5b43f9
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 0 deletions.
145 changes: 145 additions & 0 deletions src/components/FilteredPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { useCallback } from 'react';
import styled from 'styled-components';
import { Button, nullable } from '@taskany/bricks';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';

import { useUrlFilterParams } from '../hooks/useUrlFilterParams';
import { useFilterResource } from '../hooks/useFilterResource';
import { dispatchModalEvent, ModalEvent } from '../utils/dispatchModal';
import { createFilterKeys } from '../utils/hotkeys';
import { filtersPanelResetButton } from '../utils/domObjects';
import { FilterById } from '../../trpc/inferredTypes';

import { PageContent } from './Page';
import { FiltersPanel } from './FiltersPanel/FiltersPanel';
import { PresetDropdown } from './PresetDropdown';

const ModalOnEvent = dynamic(() => import('./ModalOnEvent'));
const FilterCreateForm = dynamic(() => import('./FilterCreateForm/FilterCreateForm'));
const FilterDeleteForm = dynamic(() => import('./FilterDeleteForm/FilterDeleteForm'));

interface FilteredPageProps {
isLoading: boolean;
counter?: number;
total?: number;
onFilterStar: () => Promise<void>;
filterPreset?: FilterById;
userFilters?: React.ComponentProps<typeof PresetDropdown>['presets'];
filterControls?: React.ReactNode;
}

const StyledFilterControls = styled.div`
display: flex;
align-items: center;
justify-content: flex-start;
flex: 1 0 0;
`;

const StyledResetButton = styled(Button)`
margin-left: auto;
`;

export const FilteredPage: React.FC<React.PropsWithChildren<FilteredPageProps>> = ({
counter,
total,
children,
isLoading,
onFilterStar,
filterPreset,
userFilters,
filterControls,
}) => {
const router = useRouter();
const { toggleFilterStar } = useFilterResource();

const {
currentPreset,
queryState,
queryString,
setStarredFilter,
setWatchingFilter,
setFulltextFilter,
resetQueryState,
setPreset,
batchQueryState,
} = useUrlFilterParams({
preset: filterPreset,
});

const filterStarHandler = useCallback(async () => {
if (currentPreset) {
if (currentPreset._isOwner) {
dispatchModalEvent(ModalEvent.FilterDeleteModal)();
} else {
await toggleFilterStar({
id: currentPreset.id,
direction: !currentPreset._isStarred,
});
await onFilterStar();
}
} else {
dispatchModalEvent(ModalEvent.FilterCreateModal)();
}
}, [currentPreset, toggleFilterStar, onFilterStar]);

const onFilterCreated = useCallback(
(id: string) => {
dispatchModalEvent(ModalEvent.FilterCreateModal)();
setPreset(id);
},
[setPreset],
);

const onFilterDeleteCanceled = useCallback(() => {
dispatchModalEvent(ModalEvent.FilterDeleteModal)();
}, []);

const onFilterDeleted = useCallback(
(params: string) => {
router.push(`${router.route}?${params}`);
},
[router],
);

return (
<>
<FiltersPanel
loading={isLoading}
total={total}
counter={counter}
queryState={queryState}
queryString={queryString}
preset={currentPreset}
presets={userFilters}
onSearchChange={setFulltextFilter}
onStarredChange={setStarredFilter}
onWatchingChange={setWatchingFilter}
onPresetChange={setPreset}
onFilterStar={filterStarHandler}
onFilterApply={batchQueryState}
>
<StyledFilterControls>
{filterControls}
{nullable(queryString || filterPreset, () => (
<StyledResetButton text="Reset" onClick={resetQueryState} {...filtersPanelResetButton.attr} />
))}
</StyledFilterControls>
</FiltersPanel>

<PageContent>{children}</PageContent>

{nullable(queryString, (params) => (
<ModalOnEvent event={ModalEvent.FilterCreateModal} hotkeys={createFilterKeys}>
<FilterCreateForm mode="User" params={params} onSubmit={onFilterCreated} />
</ModalOnEvent>
))}

{nullable(currentPreset, (cP) => (
<ModalOnEvent view="warn" event={ModalEvent.FilterDeleteModal}>
<FilterDeleteForm preset={cP} onSubmit={onFilterDeleted} onCancel={onFilterDeleteCanceled} />
</ModalOnEvent>
))}
</>
);
};
101 changes: 101 additions & 0 deletions src/components/FlatGoalList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
import { Table, nullable } from '@taskany/bricks';

import { GoalByIdReturnType } from '../../trpc/inferredTypes';
import { QueryState, useUrlFilterParams } from '../hooks/useUrlFilterParams';
import { trpc } from '../utils/trpcClient';
import { refreshInterval } from '../utils/config';
import { useFMPMetric } from '../utils/telemetry';

import { useGoalPreview } from './GoalPreview/GoalPreviewProvider';
import { GoalListItem } from './GoalListItem';
import { LoadMoreButton } from './LoadMoreButton/LoadMoreButton';

interface GoalListProps {
queryState?: QueryState;
setTagFilterOutside: ReturnType<typeof useUrlFilterParams>['setTagsFilterOutside'];
}

const pageSize = 20;

export const FlatGoalList: React.FC<GoalListProps> = ({ queryState, setTagFilterOutside }) => {
const { preview, setPreview } = useGoalPreview();

const [, setPage] = useState(0);
const { data, fetchNextPage, hasNextPage } = trpc.goal.getBatch.useInfiniteQuery(
{
limit: pageSize,
query: queryState,
},
{
getNextPageParam: (p) => p.nextCursor,
keepPreviousData: true,
staleTime: refreshInterval,
},
);

useFMPMetric(!!data);

const pages = data?.pages;
const goalsOnScreen = useMemo(() => pages?.flatMap((p) => p.items), [pages]);

const onFetchNextPage = useCallback(() => {
fetchNextPage();
setPage((prev) => prev++);
}, [fetchNextPage]);

useEffect(() => {
const isGoalDeletedAlready = preview && !goalsOnScreen?.some((g) => g.id === preview.id);

if (isGoalDeletedAlready) setPreview(null);
}, [goalsOnScreen, preview, setPreview]);

const selectedGoalResolver = useCallback((id: string) => id === preview?.id, [preview]);

const onGoalPrewiewShow = useCallback(
(goal: GoalByIdReturnType): MouseEventHandler<HTMLAnchorElement> =>
(e) => {
if (e.metaKey || e.ctrlKey || !goal?._shortId) return;

e.preventDefault();
setPreview(goal._shortId, goal);
},
[setPreview],
);
return (
<>
<Table>
{goalsOnScreen?.map((g) => (
<GoalListItem
createdAt={g.createdAt}
updatedAt={g.updatedAt}
id={g.id}
shortId={g._shortId}
projectId={g.projectId}
state={g.state}
title={g.title}
issuer={g.activity}
owner={g.owner}
tags={g.tags}
priority={g.priority}
comments={g._count?.comments}
estimate={g.estimate}
estimateType={g.estimateType}
participants={g.participants}
starred={g._isStarred}
watching={g._isWatching}
achivedCriteriaWeight={g._achivedCriteriaWeight}
key={g.id}
focused={selectedGoalResolver(g.id)}
onClick={onGoalPrewiewShow(g as GoalByIdReturnType)}
onTagClick={setTagFilterOutside}
/>
))}
</Table>

{nullable(hasNextPage, () => (
<LoadMoreButton onClick={onFetchNextPage} />
))}
</>
);
};
74 changes: 74 additions & 0 deletions src/components/GroupedGoalList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { MouseEventHandler, useCallback, useMemo } from 'react';
import { nullable } from '@taskany/bricks';

import { QueryState, useUrlFilterParams } from '../hooks/useUrlFilterParams';
import { refreshInterval } from '../utils/config';
import { GoalByIdReturnType } from '../../trpc/inferredTypes';
import { trpc } from '../utils/trpcClient';
import { useFMPMetric } from '../utils/telemetry';

import { LoadMoreButton } from './LoadMoreButton/LoadMoreButton';
import { useGoalPreview } from './GoalPreview/GoalPreviewProvider';
import { ProjectListItemConnected } from './ProjectListItemConnected';

interface GroupedGoalListProps {
queryState?: QueryState;
setTagFilterOutside: ReturnType<typeof useUrlFilterParams>['setTagsFilterOutside'];
}

export const projectsSize = 20;

export const GroupedGoalList: React.FC<GroupedGoalListProps> = ({ queryState, setTagFilterOutside }) => {
const { preview, setPreview } = useGoalPreview();
const { data, fetchNextPage, hasNextPage } = trpc.project.getAll.useInfiniteQuery(
{
limit: projectsSize,
goalsQuery: queryState,
},
{
getNextPageParam: (p) => p.nextCursor,
keepPreviousData: true,
staleTime: refreshInterval,
},
);

useFMPMetric(!!data);

const onGoalPrewiewShow = useCallback(
(goal: GoalByIdReturnType): MouseEventHandler<HTMLAnchorElement> =>
(e) => {
if (e.metaKey || e.ctrlKey || !goal?._shortId) return;

e.preventDefault();
setPreview(goal._shortId, goal);
},
[setPreview],
);

const selectedGoalResolver = useCallback((id: string) => id === preview?.id, [preview]);

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

return pages.flatMap((page) => page.projects);
}, [data]);

return (
<>
{projectsOnScreen.map((project) => (
<ProjectListItemConnected
key={project.id}
project={project}
onTagClick={setTagFilterOutside}
onClickProvider={onGoalPrewiewShow}
selectedResolver={selectedGoalResolver}
queryState={queryState}
/>
))}

{nullable(hasNextPage, () => (
<LoadMoreButton onClick={() => fetchNextPage()} />
))}
</>
);
};

0 comments on commit e5b43f9

Please sign in to comment.