diff --git a/next.config.js b/next.config.js index 187473dcc..eaf40c26f 100644 --- a/next.config.js +++ b/next.config.js @@ -54,15 +54,6 @@ const nextConfig = { return config; }, - async redirects() { - return [ - { - source: '/', - destination: '/goals', - permanent: true, - }, - ]; - }, }; module.exports = diff --git a/src/components/DashboardPage/DashboardPage.i18n/en.json b/src/components/DashboardPage/DashboardPage.i18n/en.json new file mode 100644 index 000000000..62302f45c --- /dev/null +++ b/src/components/DashboardPage/DashboardPage.i18n/en.json @@ -0,0 +1,7 @@ +{ + "title": "Taskany — Goals — Dashboard", + "Dashboard": "Dashboard", + "This is your personal goals bundle": "This is your personal goals bundle. Created, owned, watching projects and goals are here.", + "Reset": "Reset", + "created by": "created by" +} diff --git a/src/components/HomePage/HomePage.i18n/index.ts b/src/components/DashboardPage/DashboardPage.i18n/index.ts similarity index 100% rename from src/components/HomePage/HomePage.i18n/index.ts rename to src/components/DashboardPage/DashboardPage.i18n/index.ts diff --git a/src/components/DashboardPage/DashboardPage.i18n/ru.json b/src/components/DashboardPage/DashboardPage.i18n/ru.json new file mode 100644 index 000000000..e674c7e63 --- /dev/null +++ b/src/components/DashboardPage/DashboardPage.i18n/ru.json @@ -0,0 +1,7 @@ +{ + "title": "Taskany — Цели — Рабочий стол", + "Dashboard": "Рабочий стол", + "This is your personal goals bundle": "Здесь будут все созданные вами, назначенные на вас, а также цели и проекты, за которыми вы следите.", + "Reset": "Сбросить", + "created by": "автор" +} diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx new file mode 100644 index 000000000..fdd6ce72b --- /dev/null +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -0,0 +1,270 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import React, { MouseEventHandler, useCallback, useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { useRouter } from 'next/router'; +import { nullable, Button } from '@taskany/bricks'; + +import { refreshInterval } from '../../utils/config'; +import { ExternalPageProps } from '../../utils/declareSsrProps'; +import { ModalEvent, dispatchModalEvent } from '../../utils/dispatchModal'; +import { createFilterKeys } from '../../utils/hotkeys'; +import { useUrlFilterParams } from '../../hooks/useUrlFilterParams'; +import { useFilterResource } from '../../hooks/useFilterResource'; +import { routes } from '../../hooks/router'; +import { Page, PageContent } from '../Page'; +import { CommonHeader } from '../CommonHeader'; +import { FiltersPanel } from '../FiltersPanel/FiltersPanel'; +import { GoalsGroup } from '../GoalsGroup'; +import { GoalsListContainer } from '../GoalListItem'; +import { PageTitle } from '../PageTitle'; +import { Nullish } from '../../types/void'; +import { trpc } from '../../utils/trpcClient'; +import { FilterById, GoalByIdReturnType, ProjectByIdReturnType } from '../../../trpc/inferredTypes'; +import { ProjectItemStandalone } from '../ProjectListItem'; + +import { tr } from './DashboardPage.i18n'; + +const GoalPreview = dynamic(() => import('../GoalPreview/GoalPreview')); +const ModalOnEvent = dynamic(() => import('../ModalOnEvent')); +const FilterCreateForm = dynamic(() => import('../FilterCreateForm/FilterCreateForm')); +const FilterDeleteForm = dynamic(() => import('../FilterDeleteForm/FilterDeleteForm')); + +export const DashboardPage = ({ user, ssrTime, locale }: ExternalPageProps) => { + const router = useRouter(); + const [preview, setPreview] = useState(null); + const { toggleFilterStar } = useFilterResource(); + + const utils = trpc.useContext(); + + const presetData = trpc.filter.getById.useQuery(router.query.filter as string, { enabled: !!router.query.filter }); + + const { + currentPreset, + queryState, + queryString, + setPriorityFilter, + setStateFilter, + setTagsFilter, + setTagsFilterOutside, + setEstimateFilter, + setIssuerFilter, + setOwnerFilter, + setParticipantFilter, + setProjectFilter, + setStarredFilter, + setWatchingFilter, + setSortFilter, + setFulltextFilter, + resetQueryState, + setPreset, + } = useUrlFilterParams({ + preset: presetData?.data, + }); + + const { data, isLoading } = trpc.goal.getUserGoals.useQuery(queryState, { + keepPreviousData: true, + staleTime: refreshInterval, + }); + + const goals = data?.goals; + const meta = data?.meta; + const userFilters = trpc.filter.getUserFilters.useQuery(); + const shadowPreset = userFilters.data?.filter((f) => f.params === queryString)[0]; + + const groupsMap = + // eslint-disable-next-line no-spaced-func + (goals as NonNullable[])?.reduce<{ + [key: string]: { + project?: ProjectByIdReturnType | null; + goals: NonNullable[]; + }; + }>((r, g) => { + const k = g.projectId; + + if (k) { + if (!r[k]) { + r[k] = { + project: g.project, + goals: [], + }; + } + + r[k].goals.push(g); + } + return r; + }, Object.create(null)) || {}; + + const groups = Object.values(groupsMap); + + useEffect(() => { + const isGoalDeletedAlready = preview && !goals?.some((g) => g.id === preview.id); + + if (isGoalDeletedAlready) setPreview(null); + }, [goals, preview]); + + const onGoalPrewiewShow = useCallback( + (goal: GoalByIdReturnType): MouseEventHandler => + (e) => { + if (e.metaKey || e.ctrlKey) return; + + e.preventDefault(); + setPreview(goal); + }, + [], + ); + + const onGoalPreviewDestroy = useCallback(() => { + setPreview(null); + }, []); + + const selectedGoalResolver = useCallback((id: string) => id === preview?.id, [preview]); + + const onFilterStar = useCallback(async () => { + if (currentPreset) { + if (currentPreset._isOwner) { + dispatchModalEvent(ModalEvent.FilterDeleteModal)(); + } else { + await toggleFilterStar({ + id: currentPreset.id, + direction: !currentPreset._isStarred, + }); + await utils.filter.getById.invalidate(); + } + } else { + dispatchModalEvent(ModalEvent.FilterCreateModal)(); + } + }, [currentPreset, toggleFilterStar, utils]); + + const onFilterCreated = useCallback( + (data: Nullish) => { + dispatchModalEvent(ModalEvent.FilterCreateModal)(); + setPreset(data.id); + }, + [setPreset], + ); + + const onFilterDeleteCanceled = useCallback(() => { + dispatchModalEvent(ModalEvent.FilterDeleteModal)(); + }, []); + + const onFilterDeleted = useCallback( + (filter: FilterById) => { + router.push(`${router.route}?${filter.params}`); + }, + [router], + ); + + const defaultTitle = ; + const presetInfo = + user.activityId !== currentPreset?.activityId + ? `${tr('created by')} ${currentPreset?.activity?.user?.name}` + : undefined; + const presetTitle = ; + + const onShadowPresetTitleClick = useCallback(() => { + if (shadowPreset) setPreset(shadowPreset.id); + }, [setPreset, shadowPreset]); + const shadowPresetInfo = + user.activityId !== shadowPreset?.activityId + ? `${tr('created by')} ${shadowPreset?.activity?.user?.name}` + : undefined; + const shadowPresetTitle = ( + + ); + // eslint-disable-next-line no-nested-ternary + const title = currentPreset ? presetTitle : shadowPreset ? shadowPresetTitle : defaultTitle; + + const description = + currentPreset && currentPreset.description + ? currentPreset.description + : tr('This is your personal goals bundle'); + + return ( + + + + + {Boolean(queryString) &&