Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-1999] dev: interactive active cycle stats #5280

Merged
merged 3 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions web/core/components/core/list/list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,21 @@ export const ListItem: FC<IListItemProps> = (props) => {
)}
>
<div className="relative flex w-full items-center justify-between gap-3 overflow-hidden">
<div className="relative flex w-full items-center gap-3 overflow-hidden">
<ControlLink
href={itemLink}
target="_self"
className="flex items-center gap-4 truncate"
onClick={handleControlLinkClick}
disabled={disableLink}
>
<ControlLink
className="relative flex w-full items-center gap-3 overflow-hidden"
href={itemLink}
target="_self"
onClick={handleControlLinkClick}
disabled={disableLink}
>
<div className="flex items-center gap-4 truncate">
{prependTitleElement && <span className="flex items-center flex-shrink-0">{prependTitleElement}</span>}
<Tooltip tooltipContent={title} position="top" isMobile={isMobile}>
<span className="truncate text-sm">{title}</span>
</Tooltip>
</ControlLink>
</div>
{appendTitleElement && <span className="flex items-center flex-shrink-0">{appendTitleElement}</span>}
</div>
</ControlLink>
{quickActionElement && quickActionElement}
</div>
{actionableItems && (
Expand Down
28 changes: 22 additions & 6 deletions web/core/components/cycles/active-cycle/cycle-stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import { FC, Fragment, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import useSWR from "swr";
import { CalendarCheck } from "lucide-react";
// headless ui
import { Tab } from "@headlessui/react";
// types
import { ICycle } from "@plane/types";
import { ICycle, IIssueFilterOptions } from "@plane/types";
// ui
import { Tooltip, Loader, PriorityIcon, Avatar } from "@plane/ui";
// components
Expand All @@ -32,10 +31,11 @@ export type ActiveCycleStatsProps = {
projectId: string;
cycle: ICycle | null;
cycleId?: string | null;
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string[], redirect?: boolean) => void;
};

export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
const { workspaceSlug, projectId, cycle, cycleId } = props;
const { workspaceSlug, projectId, cycle, cycleId, handleFiltersUpdate } = props;

const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees");

Expand All @@ -59,6 +59,7 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
} = useIssues(EIssuesStoreType.CYCLE);
const {
issue: { getIssueById },
setPeekIssue,
} = useIssueDetail();

const { currentProjectDetails } = useProject();
Expand Down Expand Up @@ -171,10 +172,15 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
if (!issue) return null;

return (
<Link
<div
key={issue.id}
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
className="group flex cursor-pointer items-center justify-between gap-2 rounded-md hover:bg-custom-background-90 p-1"
onClick={() => {
if (issue.id) {
setPeekIssue({ workspaceSlug, projectId, issueId: issue.id });
handleFiltersUpdate("priority", ["urgent", "high"], true);
}
}}
>
<div className="flex items-center gap-1.5 flex-grow w-full min-w-24 truncate">
<PriorityIcon priority={issue.priority} withContainer size={12} />
Expand Down Expand Up @@ -215,7 +221,7 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
</Tooltip>
)}
</div>
</Link>
</div>
);
})}
{(cycleIssueDetails.nextPageResults === undefined || cycleIssueDetails.nextPageResults) && (
Expand Down Expand Up @@ -262,6 +268,11 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
}
completed={assignee.completed_issues}
total={assignee.total_issues}
onClick={() => {
if (assignee.assignee_id) {
handleFiltersUpdate("assignees", [assignee.assignee_id], true);
}
}}
/>
);
else
Expand Down Expand Up @@ -317,6 +328,11 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
}
completed={label.completed_issues}
total={label.total_issues}
onClick={() => {
if (label.label_id) {
handleFiltersUpdate("labels", [label.label_id], true);
}
}}
/>
))
) : (
Expand Down
34 changes: 21 additions & 13 deletions web/core/components/cycles/active-cycle/progress.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
"use client";

import { FC } from "react";
import Link from "next/link";
import { observer } from "mobx-react";
// types
import { ICycle } from "@plane/types";
import { ICycle, IIssueFilterOptions } from "@plane/types";
// ui
import { LinearProgressIndicator, Loader } from "@plane/ui";
// components
import { EmptyState } from "@/components/empty-state";
// constants
import { PROGRESS_STATE_GROUPS_DETAILS } from "@/constants/common";
import { EmptyStateType } from "@/constants/empty-state";
// hooks
import { useProjectState } from "@/hooks/store";

export type ActiveCycleProgressProps = {
workspaceSlug: string;
projectId: string;
cycle: ICycle | null;
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string[], redirect?: boolean) => void;
};

export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
const { workspaceSlug, projectId, cycle } = props;
export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = observer((props) => {
const { cycle, handleFiltersUpdate } = props;
// store hooks
const { groupedProjectStates } = useProjectState();

const progressIndicatorData = PROGRESS_STATE_GROUPS_DETAILS.map((group, index) => ({
id: index,
Expand All @@ -38,10 +41,7 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
: {};

return cycle ? (
<Link
href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`}
className="flex flex-col min-h-[17rem] gap-5 py-4 px-3.5 bg-custom-background-100 border border-custom-border-200 rounded-lg"
>
<div className="flex flex-col min-h-[17rem] gap-5 py-4 px-3.5 bg-custom-background-100 border border-custom-border-200 rounded-lg">
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between gap-4">
<h3 className="text-base text-custom-text-300 font-semibold">Progress</h3>
Expand All @@ -62,7 +62,15 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
<>
{groupedIssues[group] > 0 && (
<div key={index}>
<div className="flex items-center justify-between gap-2 text-sm">
<div
className="flex items-center justify-between gap-2 text-sm cursor-pointer"
onClick={() => {
if (groupedProjectStates) {
const states = groupedProjectStates[group].map((state) => state.id);
handleFiltersUpdate("state", states, true);
}
}}
>
<div className="flex items-center gap-1.5">
<span
className="block h-3 w-3 rounded-full"
Expand Down Expand Up @@ -95,10 +103,10 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_PROGRESS_EMPTY_STATE} layout="screen-simple" size="sm" />
</div>
)}
</Link>
</div>
) : (
<Loader className="flex flex-col min-h-[17rem] gap-5 bg-custom-background-100 border border-custom-border-200 rounded-lg">
<Loader.Item width="100%" height="100%" />
</Loader>
);
};
});
44 changes: 41 additions & 3 deletions web/core/components/cycles/active-cycle/root.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"use client";

import { useCallback } from "react";
import isEqual from "lodash/isEqual";
import { observer } from "mobx-react";
import { useRouter } from "next/navigation";
import useSWR from "swr";
// ui
import { Disclosure } from "@headlessui/react";
// types
import { IIssueFilterOptions } from "@plane/types";
// ui
import { Loader } from "@plane/ui";
// components
import {
Expand All @@ -16,8 +21,9 @@ import {
import { EmptyState } from "@/components/empty-state";
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
// hooks
import { useCycle } from "@/hooks/store";
import { useCycle, useIssues } from "@/hooks/store";

interface IActiveCycleDetails {
workspaceSlug: string;
Expand All @@ -27,7 +33,12 @@ interface IActiveCycleDetails {
export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) => {
// props
const { workspaceSlug, projectId } = props;
// router
const router = useRouter();
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectActiveCycle, fetchActiveCycle, currentProjectActiveCycleId, getActiveCycleById } = useCycle();
// derived values
const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null;
Expand All @@ -37,6 +48,32 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
workspaceSlug && projectId ? () => fetchActiveCycle(workspaceSlug, projectId) : null
);

const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string[], redirect?: boolean) => {
if (!workspaceSlug || !projectId || !currentProjectActiveCycleId) return;

const newFilters: IIssueFilterOptions = {};
Object.keys(issueFilters?.filters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = [];
});

let newValues: string[] = [];

if (isEqual(newValues, value)) newValues = [];
else newValues = value;

updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ ...newFilters, [key]: newValues },
currentProjectActiveCycleId.toString()
);
if (redirect) router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${currentProjectActiveCycleId}`);
},
[workspaceSlug, projectId, currentProjectActiveCycleId, issueFilters, updateFilters, router]
);

// show loader if active cycle is loading
if (!currentProjectActiveCycle && isLoading)
return (
Expand Down Expand Up @@ -69,7 +106,7 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
)}
<div className="bg-custom-background-100 pt-3 pb-6 px-6">
<div className="grid grid-cols-1 bg-custom-background-100 gap-3 lg:grid-cols-2 xl:grid-cols-3">
<ActiveCycleProgress workspaceSlug={workspaceSlug} projectId={projectId} cycle={activeCycle} />
<ActiveCycleProgress cycle={activeCycle} handleFiltersUpdate={handleFiltersUpdate} />
<ActiveCycleProductivity
workspaceSlug={workspaceSlug}
projectId={projectId}
Expand All @@ -80,6 +117,7 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
projectId={projectId}
cycle={activeCycle}
cycleId={currentProjectActiveCycleId}
handleFiltersUpdate={handleFiltersUpdate}
/>
</div>
</div>
Expand Down
Loading
Loading