chore: optimised issue activity and updated the popover component in issue detail and peek overview (#5208)
This commit is contained in:
parent
31fe9a1a02
commit
f5027f4268
8 changed files with 94 additions and 81 deletions
28
web/ce/components/issues/worklog/activity/filter-root.tsx
Normal file
28
web/ce/components/issues/worklog/activity/filter-root.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
// components
|
||||
import { ActivityFilter } from "@/components/issues";
|
||||
// plane web constants
|
||||
import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS, TActivityFilterOption } from "@/plane-web/constants/issues";
|
||||
|
||||
export type TActivityFilterRoot = {
|
||||
selectedFilters: TActivityFilters[];
|
||||
toggleFilter: (filter: TActivityFilters) => void;
|
||||
};
|
||||
|
||||
export const ActivityFilterRoot: FC<TActivityFilterRoot> = (props) => {
|
||||
const { selectedFilters, toggleFilter } = props;
|
||||
|
||||
const filters: TActivityFilterOption[] = Object.entries(ACTIVITY_FILTER_TYPE_OPTIONS).map(([key, value]) => {
|
||||
const filterKey = key as TActivityFilters;
|
||||
return {
|
||||
key: filterKey,
|
||||
label: value.label,
|
||||
isSelected: selectedFilters.includes(filterKey),
|
||||
onClick: () => toggleFilter(filterKey),
|
||||
};
|
||||
});
|
||||
|
||||
return <ActivityFilter selectedFilters={selectedFilters} filterOptions={filters} />;
|
||||
};
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
export * from "./root";
|
||||
export * from "./worklog-create-button";
|
||||
|
||||
export * from "./filter-root";
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ export const ACTIVITY_FILTER_TYPE_OPTIONS: Record<EActivityFilterType, { label:
|
|||
|
||||
export const defaultActivityFilters: TActivityFilters[] = [EActivityFilterType.ACTIVITY, EActivityFilterType.COMMENT];
|
||||
|
||||
export type TActivityFilterOption = {
|
||||
key: EActivityFilterType;
|
||||
label: string;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const filterActivityOnSelectedFilters = (
|
||||
activity: TIssueActivityComment[],
|
||||
filter: TActivityFilters[]
|
||||
|
|
|
|||
|
|
@ -1,85 +1,59 @@
|
|||
import React, { FC, Fragment } from "react";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Check, ListFilter } from "lucide-react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
import { Button, PopoverMenu } from "@plane/ui";
|
||||
// helper
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// constants
|
||||
import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS } from "@/plane-web/constants/issues";
|
||||
import { TActivityFilterOption, TActivityFilters } from "@/plane-web/constants/issues";
|
||||
|
||||
type Props = {
|
||||
type TActivityFilter = {
|
||||
selectedFilters: TActivityFilters[];
|
||||
toggleFilter: (filter: TActivityFilters) => void;
|
||||
filterOptions: TActivityFilterOption[];
|
||||
};
|
||||
|
||||
export const ActivityFilter: FC<Props> = observer((props) => {
|
||||
const { selectedFilters, toggleFilter } = props;
|
||||
return (
|
||||
<Popover as="div" className="relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
size="sm"
|
||||
prependIcon={<ListFilter className="h-3 w-3" />}
|
||||
className="relative"
|
||||
>
|
||||
<span className={`${open ? "text-custom-text-100" : "text-custom-text-200"}`}>Filters</span>
|
||||
</Button>
|
||||
</Popover.Button>
|
||||
export const ActivityFilter: FC<TActivityFilter> = observer((props) => {
|
||||
const { selectedFilters = [], filterOptions } = props;
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
return (
|
||||
<PopoverMenu
|
||||
buttonClassName="outline-none"
|
||||
button={
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
size="sm"
|
||||
prependIcon={<ListFilter className="h-3 w-3" />}
|
||||
className="relative"
|
||||
>
|
||||
<span className="text-custom-text-200">Filters</span>
|
||||
</Button>
|
||||
}
|
||||
panelClassName="p-2 rounded-md border border-custom-border-200 bg-custom-background-100"
|
||||
data={filterOptions}
|
||||
keyExtractor={(item) => item.key}
|
||||
render={(item) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 text-sm cursor-pointer px-2 p-1 transition-all hover:bg-custom-background-80 rounded-sm"
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 w-3 h-3 flex justify-center items-center rounded-sm transition-all bg-custom-background-90",
|
||||
{
|
||||
"bg-custom-primary text-white": item.isSelected,
|
||||
"bg-custom-background-80 text-custom-text-400": item.isSelected && selectedFilters.length === 1,
|
||||
"bg-custom-background-90": !item.isSelected,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Popover.Panel className="absolute mt-2 right-0 z-10 min-w-40">
|
||||
<div className="p-2 rounded-md border border-custom-border-200 bg-custom-background-100">
|
||||
{Object.keys(ACTIVITY_FILTER_TYPE_OPTIONS).map((key) => {
|
||||
const filterKey = key as TActivityFilters;
|
||||
const filter = ACTIVITY_FILTER_TYPE_OPTIONS[filterKey];
|
||||
const isSelected = selectedFilters.includes(filterKey);
|
||||
return (
|
||||
<div
|
||||
key={filterKey}
|
||||
className="flex items-center gap-2 text-sm cursor-pointer px-2 p-1 transition-all hover:bg-custom-background-80 rounded-sm"
|
||||
onClick={() => toggleFilter(filterKey)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 w-3 h-3 flex justify-center items-center rounded-sm transition-all bg-custom-background-90",
|
||||
{
|
||||
"bg-custom-primary text-white": isSelected,
|
||||
"bg-custom-background-80 text-custom-text-400": isSelected && selectedFilters.length === 1,
|
||||
"bg-custom-background-90": !isSelected,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{isSelected && <Check className="h-2.5 w-2.5" />}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"whitespace-nowrap",
|
||||
isSelected ? "text-custom-text-100" : "text-custom-text-200"
|
||||
)}
|
||||
>
|
||||
{filter.label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
{item.isSelected && <Check className="h-2.5 w-2.5" />}
|
||||
</div>
|
||||
<div className={cn("whitespace-nowrap", item.isSelected ? "text-custom-text-100" : "text-custom-text-200")}>
|
||||
{item.label}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { TIssueComment } from "@plane/types";
|
|||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ActivityFilter, IssueCommentCreate } from "@/components/issues";
|
||||
import { IssueCommentCreate } from "@/components/issues";
|
||||
import { IssueActivityCommentRoot } from "@/components/issues/issue-detail";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { IssueActivityWorklogCreateButton } from "@/plane-web/components/issues/worklog";
|
||||
import { ActivityFilterRoot, IssueActivityWorklogCreateButton } from "@/plane-web/components/issues/worklog";
|
||||
// plane web constants
|
||||
import { TActivityFilters, defaultActivityFilters } from "@/plane-web/constants/issues";
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
|||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<ActivityFilter selectedFilters={selectedFilters} toggleFilter={toggleFilter} />
|
||||
<ActivityFilterRoot selectedFilters={selectedFilters} toggleFilter={toggleFilter} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/components/issues/worklog/activity/filter-root";
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
export * from "./root";
|
||||
export * from "./worklog-create-button";
|
||||
|
||||
export * from "./filter-root";
|
||||
|
|
|
|||
|
|
@ -323,14 +323,13 @@ export const convertMinutesToHoursAndMinutes = (mins: number): { hours: number;
|
|||
};
|
||||
|
||||
/**
|
||||
* @description converts minutes to days, hours and minutes
|
||||
* @description converts minutes to hours and minutes string
|
||||
* @param { number } totalMinutes
|
||||
* @returns { string } days, hours and minutes
|
||||
* @returns { string } 0h 0m
|
||||
* @example convertMinutesToHoursAndMinutes(150) // Output: 2h 10m
|
||||
*/
|
||||
export const convertMinutesToDaysHoursMinutes = (totalMinutes: number): string => {
|
||||
const days = Math.floor(totalMinutes / (60 * 24));
|
||||
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
export const convertMinutesToHoursMinutesString = (totalMinutes: number): string => {
|
||||
const { hours, minutes } = convertMinutesToHoursAndMinutes(totalMinutes);
|
||||
|
||||
return `${days ? `${days}d ` : ``}${hours ? `${hours}h ` : ``}${minutes ? `${minutes}m ` : ``} `;
|
||||
return `${hours ? `${hours}h ` : ``}${minutes ? `${minutes}m ` : ``}`;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue