[WEB-3751] chore: work item state icon improvement (#6960)
* chore: return order based on group * chore: order for workspace stats endpoint * chore: state response updated * chore: state icon types updated * chore: state icon updated * chore: state settings new icon implementation * chore: icon implementation * chore: code refactor * chore: code refactor * chore: code refactor * fix: order field type --------- Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
This commit is contained in:
parent
baabb82669
commit
f5449c8f93
33 changed files with 376 additions and 145 deletions
|
|
@ -2,6 +2,7 @@ import { Command } from "cmdk";
|
|||
import { observer } from "mobx-react";
|
||||
import { Check } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { Spinner, StateGroupIcon } from "@plane/ui";
|
||||
// store hooks
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
|
|
@ -26,7 +27,12 @@ export const ChangeWorkItemStateList = observer((props: TChangeWorkItemStateList
|
|||
projectStates.map((state) => (
|
||||
<Command.Item key={state.id} onSelect={() => handleStateChange(state.id)} className="focus:outline-none">
|
||||
<div className="flex items-center space-x-3">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
|
||||
<StateGroupIcon
|
||||
stateGroup={state.group}
|
||||
color={state.color}
|
||||
size={EIconSize.LG}
|
||||
percentage={state?.order}
|
||||
/>
|
||||
<p>{state.name}</p>
|
||||
</div>
|
||||
<div>{state.id === currentStateId && <Check className="h-3 w-3" />}</div>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { ArchiveX } from "lucide-react";
|
||||
// types
|
||||
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel, EIconSize } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -42,7 +42,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
|||
query: state.name,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.LG} />
|
||||
{state.name}
|
||||
</div>
|
||||
),
|
||||
|
|
@ -139,7 +139,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-center justify-between gap-2 px-5 py-4">
|
||||
<div className="ppy sm:py-10 flex w-full items-center justify-between gap-2 px-5 py-4">
|
||||
<div className="w-1/2 text-sm font-medium">
|
||||
{t("project_settings.automations.auto-close.auto_close_status")}
|
||||
</div>
|
||||
|
|
@ -149,18 +149,12 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
|||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedOption ? (
|
||||
<StateGroupIcon
|
||||
stateGroup={selectedOption.group}
|
||||
color={selectedOption.color}
|
||||
height="16px"
|
||||
width="16px"
|
||||
/>
|
||||
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} size="lg" />
|
||||
) : currentDefaultState ? (
|
||||
<StateGroupIcon
|
||||
stateGroup={currentDefaultState.group}
|
||||
color={currentDefaultState.color}
|
||||
height="16px"
|
||||
width="16px"
|
||||
size={EIconSize.LG}
|
||||
/>
|
||||
) : (
|
||||
<DoubleCircleIcon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
|
|
|
|||
|
|
@ -98,7 +98,12 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||
query: `${state?.name}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<StateGroupIcon stateGroup={state?.group ?? "backlog"} color={state?.color} className="h-3 w-3 flex-shrink-0" />
|
||||
<StateGroupIcon
|
||||
stateGroup={state?.group ?? "backlog"}
|
||||
color={state?.color}
|
||||
className="size-4 flex-shrink-0"
|
||||
percentage={state?.order}
|
||||
/>
|
||||
<span className="flex-grow truncate text-left">{state?.name}</span>
|
||||
</div>
|
||||
),
|
||||
|
|
@ -179,7 +184,8 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||
<StateGroupIcon
|
||||
stateGroup={selectedState?.group ?? "backlog"}
|
||||
color={selectedState?.color ?? "rgba(var(--color-text-300))"}
|
||||
className="h-3 w-3 flex-shrink-0"
|
||||
className="size-4 flex-shrink-0"
|
||||
percentage={selectedState?.order}
|
||||
/>
|
||||
)}
|
||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||
|
|
|
|||
|
|
@ -95,7 +95,12 @@ export const RecentIssue = observer((props: BlockProps) => {
|
|||
<div className="flex gap-4">
|
||||
<Tooltip tooltipHeading="State" tooltipContent={state?.name ?? "State"}>
|
||||
<div>
|
||||
<StateGroupIcon stateGroup={state?.group ?? "backlog"} color={state?.color} className="h-4 w-4 my-auto" />
|
||||
<StateGroupIcon
|
||||
stateGroup={state?.group ?? "backlog"}
|
||||
color={state?.color}
|
||||
className="h-4 w-4 my-auto"
|
||||
percentage={state?.order}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipHeading="Priority" tooltipContent={issueDetails?.priority ?? "Priority"}>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { X } from "lucide-react";
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { StateGroupIcon, Tag } from "@plane/ui";
|
||||
// hooks
|
||||
import { useProjectInbox, useProjectState } from "@/hooks/store";
|
||||
|
|
@ -30,7 +31,7 @@ export const InboxIssueAppliedFiltersState: FC = observer(() => {
|
|||
return (
|
||||
<div key={value} className="relative flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
<div className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden">
|
||||
<StateGroupIcon color={optionDetail.color} stateGroup={optionDetail.group} height="12px" width="12px" />
|
||||
<StateGroupIcon color={optionDetail.color} stateGroup={optionDetail.group} size={EIconSize.SM} />
|
||||
</div>
|
||||
<div className="text-xs truncate">{optionDetail?.name}</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { IState } from "@plane/types";
|
||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -55,7 +56,14 @@ export const FilterState: FC<Props> = observer((props) => {
|
|||
key={state?.id}
|
||||
isChecked={filterValue?.includes(state?.id) ? true : false}
|
||||
onClick={() => handleInboxIssueFilters("state", handleFilterValue(state.id))}
|
||||
icon={<StateGroupIcon color={state.color} stateGroup={state.group} height="12px" width="12px" />}
|
||||
icon={
|
||||
<StateGroupIcon
|
||||
color={state.color}
|
||||
stateGroup={state.group}
|
||||
size={EIconSize.SM}
|
||||
percentage={state?.order}
|
||||
/>
|
||||
}
|
||||
title={state.name}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react";
|
|||
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { TStateGroups } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ export const AppliedStateGroupFilters: React.FC<Props> = observer((props) => {
|
|||
<>
|
||||
{values.map((stateGroup) => (
|
||||
<div key={stateGroup} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
<StateGroupIcon stateGroup={stateGroup as TStateGroups} height="12px" width="12px" />
|
||||
<StateGroupIcon stateGroup={stateGroup as TStateGroups} size={EIconSize.SM} />
|
||||
{stateGroup}
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
|||
// icons
|
||||
import { X } from "lucide-react";
|
||||
import { IState } from "@plane/types";
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// types
|
||||
|
||||
|
|
@ -27,7 +28,12 @@ export const AppliedStateFilters: React.FC<Props> = observer((props) => {
|
|||
|
||||
return (
|
||||
<div key={stateId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
<StateGroupIcon color={stateDetails.color} stateGroup={stateDetails.group} height="12px" width="12px" />
|
||||
<StateGroupIcon
|
||||
color={stateDetails.color}
|
||||
stateGroup={stateDetails.group}
|
||||
size={EIconSize.SM}
|
||||
percentage={stateDetails?.order}
|
||||
/>
|
||||
{stateDetails.name}
|
||||
{editable && (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import React, { useMemo, useState } from "react";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { IState } from "@plane/types";
|
||||
// components
|
||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||
|
|
@ -56,7 +57,14 @@ export const FilterState: React.FC<Props> = observer((props) => {
|
|||
key={state.id}
|
||||
isChecked={appliedFilters?.includes(state.id) ? true : false}
|
||||
onClick={() => handleUpdate(state.id)}
|
||||
icon={<StateGroupIcon stateGroup={state.group} color={state.color} />}
|
||||
icon={
|
||||
<StateGroupIcon
|
||||
stateGroup={state.group}
|
||||
color={state.color}
|
||||
size={EIconSize.MD}
|
||||
percentage={state?.order}
|
||||
/>
|
||||
}
|
||||
title={state.name}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import uniq from "lodash/uniq";
|
|||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import { ContrastIcon } from "lucide-react";
|
||||
// plane types
|
||||
import { EIssuesStoreType, ISSUE_PRIORITIES, STATE_GROUPS } from "@plane/constants";
|
||||
import { EIconSize, EIssuesStoreType, ISSUE_PRIORITIES, STATE_GROUPS } from "@plane/constants";
|
||||
import {
|
||||
GroupByColumnTypes,
|
||||
IGroupByColumn,
|
||||
|
|
@ -198,8 +198,8 @@ const getStateColumns = (): IGroupByColumn[] | undefined => {
|
|||
id: state.id,
|
||||
name: state.name,
|
||||
icon: (
|
||||
<div className="h-3.5 w-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} width="14" height="14" />
|
||||
<div className="size-4 rounded-full">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.LG} percentage={state.order} />
|
||||
</div>
|
||||
),
|
||||
payload: { state_id: state.id },
|
||||
|
|
@ -213,8 +213,8 @@ const getStateGroupColumns = (): IGroupByColumn[] => {
|
|||
id: stateGroup.key,
|
||||
name: stateGroup.label,
|
||||
icon: (
|
||||
<div className="h-3.5 w-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup.key} width="14" height="14" />
|
||||
<div className="size-4 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup.key} size={EIconSize.LG} />
|
||||
</div>
|
||||
),
|
||||
payload: {},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { FC, useState, useRef } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { ChevronDown, Plus } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
|
|
@ -74,7 +75,7 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
|
|||
<ChevronDown className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="flex-shrink-0 w-6 h-6 rounded flex justify-center items-center overflow-hidden">
|
||||
<StateGroupIcon stateGroup={groupKey} height="16px" width="16px" />
|
||||
<StateGroupIcon stateGroup={groupKey} size={EIconSize.XL} />
|
||||
</div>
|
||||
<div className="text-base font-medium text-custom-text-200 capitalize px-1">{groupKey}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { SetStateAction } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { GripVertical, Pencil } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// local imports
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
import { StateDelete, StateMarksAsDefault } from "./options";
|
||||
|
||||
type TBaseStateItemTitleProps = {
|
||||
|
|
@ -28,6 +30,11 @@ export type TStateItemTitleProps = TEnabledStateItemTitleProps | TDisabledStateI
|
|||
|
||||
export const StateItemTitle = observer((props: TStateItemTitleProps) => {
|
||||
const { stateCount, setUpdateStateModal, disabled, state, shouldShowDescription = true } = props;
|
||||
// store hooks
|
||||
const { getStatePercentageInGroup } = useProjectState();
|
||||
// derived values
|
||||
const statePercentage = getStatePercentageInGroup(state.id);
|
||||
const percentage = statePercentage ? statePercentage / 100 : undefined;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 w-full justify-between">
|
||||
|
|
@ -40,7 +47,7 @@ export const StateItemTitle = observer((props: TStateItemTitleProps) => {
|
|||
)}
|
||||
{/* state icon */}
|
||||
<div className="flex-shrink-0">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} className={"size-3.5"} />
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.XL} percentage={percentage} />
|
||||
</div>
|
||||
{/* state title and description */}
|
||||
<div className="text-sm px-2 min-h-5">
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ export interface IStateStore {
|
|||
stateId: string,
|
||||
payload: Partial<IState>
|
||||
) => Promise<void>;
|
||||
|
||||
getStatePercentageInGroup: (stateId: string | null | undefined) => number | undefined;
|
||||
}
|
||||
|
||||
export class StateStore implements IStateStore {
|
||||
|
|
@ -303,4 +305,27 @@ export class StateStore implements IStateStore {
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the percentage position of a state within its group based on sequence
|
||||
* @param stateId The ID of the state to find the percentage for
|
||||
* @returns The percentage position of the state in its group (0-100), or -1 if not found
|
||||
*/
|
||||
getStatePercentageInGroup = computedFn((stateId: string | null | undefined) => {
|
||||
if (!stateId || !this.stateMap[stateId]) return -1;
|
||||
|
||||
const state = this.stateMap[stateId];
|
||||
const group = state.group;
|
||||
|
||||
if (!group || !this.groupedProjectStates || !this.groupedProjectStates[group]) return -1;
|
||||
|
||||
// Get all states in the same group
|
||||
const statesInGroup = this.groupedProjectStates[group];
|
||||
const stateIndex = statesInGroup.findIndex((s) => s.id === stateId);
|
||||
|
||||
if (stateIndex === -1) return undefined;
|
||||
|
||||
// Calculate percentage: ((index + 1) / totalLength) * 100
|
||||
return ((stateIndex + 1) / statesInGroup.length) * 100;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue