fix: minor refactoring changes for state dropdowns
This commit is contained in:
parent
da469dac18
commit
952eee8d55
39 changed files with 291 additions and 266 deletions
|
|
@ -675,6 +675,16 @@
|
||||||
"disconnecting": "Disconnecting",
|
"disconnecting": "Disconnecting",
|
||||||
"installing": "Installing",
|
"installing": "Installing",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
|
"reset": "Reset",
|
||||||
|
"live": "Live",
|
||||||
|
"change_history": "Change History",
|
||||||
|
"coming_soon": "Coming soon",
|
||||||
|
"members": "Members",
|
||||||
|
"you": "You",
|
||||||
|
"upgrade_cta": {
|
||||||
|
"higher_subscription": "Upgrade to higher subscription",
|
||||||
|
"talk_to_sales": "Talk to sales"
|
||||||
|
},
|
||||||
"category": "Category",
|
"category": "Category",
|
||||||
"categories": "Categories",
|
"categories": "Categories",
|
||||||
"saving": "Saving",
|
"saving": "Saving",
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@
|
||||||
"activity": "Actividad",
|
"activity": "Actividad",
|
||||||
"appearance": "Apariencia",
|
"appearance": "Apariencia",
|
||||||
"notifications": "Notificaciones",
|
"notifications": "Notificaciones",
|
||||||
|
"connections": "Conexiones",
|
||||||
"workspaces": "Espacios de trabajo",
|
"workspaces": "Espacios de trabajo",
|
||||||
"create_workspace": "Crear espacio de trabajo",
|
"create_workspace": "Crear espacio de trabajo",
|
||||||
"invitations": "Invitaciones",
|
"invitations": "Invitaciones",
|
||||||
|
|
@ -291,6 +292,7 @@
|
||||||
"workspace_logo": "Logo del espacio de trabajo",
|
"workspace_logo": "Logo del espacio de trabajo",
|
||||||
"new_issue": "Nuevo elemento de trabajo",
|
"new_issue": "Nuevo elemento de trabajo",
|
||||||
"your_work": "Tu trabajo",
|
"your_work": "Tu trabajo",
|
||||||
|
"workspace_dashboards": "Paneles de control",
|
||||||
"drafts": "Borradores",
|
"drafts": "Borradores",
|
||||||
"projects": "Proyectos",
|
"projects": "Proyectos",
|
||||||
"views": "Vistas",
|
"views": "Vistas",
|
||||||
|
|
@ -845,6 +847,16 @@
|
||||||
"disconnecting": "Desconectando",
|
"disconnecting": "Desconectando",
|
||||||
"installing": "Instalando",
|
"installing": "Instalando",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
|
"reset": "Reiniciar",
|
||||||
|
"live": "En vivo",
|
||||||
|
"change_history": "Historial de cambios",
|
||||||
|
"coming_soon": "Próximamente",
|
||||||
|
"members": "Miembros",
|
||||||
|
"you": "Tú",
|
||||||
|
"upgrade_cta": {
|
||||||
|
"higher_subscription": "Mejorar a una suscripción más alta",
|
||||||
|
"talk_to_sales": "Hablar con ventas"
|
||||||
|
},
|
||||||
"category": "Categoría",
|
"category": "Categoría",
|
||||||
"categories": "Categorías",
|
"categories": "Categorías",
|
||||||
"saving": "Guardando",
|
"saving": "Guardando",
|
||||||
|
|
|
||||||
|
|
@ -845,6 +845,16 @@
|
||||||
"disconnecting": "Déconnexion",
|
"disconnecting": "Déconnexion",
|
||||||
"installing": "Installation",
|
"installing": "Installation",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
|
"reset": "Réinitialiser",
|
||||||
|
"live": "En direct",
|
||||||
|
"change_history": "Historique des modifications",
|
||||||
|
"coming_soon": "À venir",
|
||||||
|
"members": "Membres",
|
||||||
|
"you": "Vous",
|
||||||
|
"upgrade_cta": {
|
||||||
|
"higher_subscription": "Passer à une abonnement plus élevé",
|
||||||
|
"talk_to_sales": "Parler à la vente"
|
||||||
|
},
|
||||||
"category": "Catégorie",
|
"category": "Catégorie",
|
||||||
"categories": "Catégories",
|
"categories": "Catégories",
|
||||||
"saving": "Enregistrement",
|
"saving": "Enregistrement",
|
||||||
|
|
|
||||||
|
|
@ -845,6 +845,16 @@
|
||||||
"disconnecting": "切断中",
|
"disconnecting": "切断中",
|
||||||
"installing": "インストール中",
|
"installing": "インストール中",
|
||||||
"install": "インストール",
|
"install": "インストール",
|
||||||
|
"reset": "リセット",
|
||||||
|
"live": "ライブ",
|
||||||
|
"change_history": "変更履歴",
|
||||||
|
"coming_soon": "近日公開",
|
||||||
|
"members": "メンバー",
|
||||||
|
"you": "あなた",
|
||||||
|
"upgrade_cta": {
|
||||||
|
"higher_subscription": "高いサブスクリプションにアップグレード",
|
||||||
|
"talk_to_sales": "セールスに連絡"
|
||||||
|
},
|
||||||
"category": "カテゴリー",
|
"category": "カテゴリー",
|
||||||
"categories": "カテゴリーズ",
|
"categories": "カテゴリーズ",
|
||||||
"saving": "セービング",
|
"saving": "セービング",
|
||||||
|
|
|
||||||
|
|
@ -845,6 +845,16 @@
|
||||||
"disconnecting": "正在断开连接",
|
"disconnecting": "正在断开连接",
|
||||||
"installing": "正在安装",
|
"installing": "正在安装",
|
||||||
"install": "安装",
|
"install": "安装",
|
||||||
|
"reset": "重置",
|
||||||
|
"live": "实时",
|
||||||
|
"change_history": "变更历史",
|
||||||
|
"coming_soon": "即将推出",
|
||||||
|
"members": "成员",
|
||||||
|
"you": "你",
|
||||||
|
"upgrade_cta": {
|
||||||
|
"higher_subscription": "升级到更高订阅",
|
||||||
|
"talk_to_sales": "联系销售"
|
||||||
|
},
|
||||||
"category": "类别",
|
"category": "类别",
|
||||||
"categories": "类别",
|
"categories": "类别",
|
||||||
"saving": "保存中",
|
"saving": "保存中",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
import { Tooltip2 } from "@blueprintjs/popover2";
|
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "../../helpers";
|
import { cn } from "../../helpers";
|
||||||
|
|
||||||
|
|
@ -23,7 +23,6 @@ export type TPosition =
|
||||||
interface ITooltipProps {
|
interface ITooltipProps {
|
||||||
tooltipHeading?: string;
|
tooltipHeading?: string;
|
||||||
tooltipContent: string | React.ReactNode;
|
tooltipContent: string | React.ReactNode;
|
||||||
jsxContent?: string | React.ReactNode;
|
|
||||||
position?: TPosition;
|
position?: TPosition;
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|
@ -39,14 +38,13 @@ export const Tooltip: React.FC<ITooltipProps> = ({
|
||||||
tooltipContent,
|
tooltipContent,
|
||||||
position = "top",
|
position = "top",
|
||||||
children,
|
children,
|
||||||
jsxContent,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
className = "",
|
className = "",
|
||||||
openDelay = 200,
|
openDelay = 200,
|
||||||
closeDelay,
|
closeDelay,
|
||||||
isMobile = false,
|
isMobile = false,
|
||||||
renderByDefault = true, //FIXME: tooltip should always render on hover and not by default, this is a temporary fix
|
renderByDefault = true, //FIXME: tooltip should always render on hover and not by default, this is a temporary fix
|
||||||
}: ITooltipProps) => {
|
}) => {
|
||||||
const toolTipRef = useRef<HTMLDivElement | null>(null);
|
const toolTipRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const [shouldRender, setShouldRender] = useState(renderByDefault);
|
const [shouldRender, setShouldRender] = useState(renderByDefault);
|
||||||
|
|
@ -81,9 +79,6 @@ export const Tooltip: React.FC<ITooltipProps> = ({
|
||||||
hoverOpenDelay={openDelay}
|
hoverOpenDelay={openDelay}
|
||||||
hoverCloseDelay={closeDelay}
|
hoverCloseDelay={closeDelay}
|
||||||
content={
|
content={
|
||||||
jsxContent ? (
|
|
||||||
<>{jsxContent}</>
|
|
||||||
) : (
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative block z-50 max-w-xs gap-1 overflow-hidden break-words rounded-md bg-custom-background-100 p-2 text-xs text-custom-text-200 shadow-md",
|
"relative block z-50 max-w-xs gap-1 overflow-hidden break-words rounded-md bg-custom-background-100 p-2 text-xs text-custom-text-200 shadow-md",
|
||||||
|
|
@ -96,7 +91,6 @@ export const Tooltip: React.FC<ITooltipProps> = ({
|
||||||
{tooltipHeading && <h5 className="font-medium text-custom-text-100">{tooltipHeading}</h5>}
|
{tooltipHeading && <h5 className="font-medium text-custom-text-100">{tooltipHeading}</h5>}
|
||||||
{tooltipContent}
|
{tooltipContent}
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
position={position}
|
position={position}
|
||||||
renderTarget={({
|
renderTarget={({
|
||||||
|
|
|
||||||
1
web/ce/components/command-palette/actions/index.ts
Normal file
1
web/ce/components/command-palette/actions/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./work-item-actions";
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Command } from "cmdk";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
// plane imports
|
||||||
|
import { Spinner, StateGroupIcon } from "@plane/ui";
|
||||||
|
// store hooks
|
||||||
|
import { useProjectState } from "@/hooks/store";
|
||||||
|
|
||||||
|
export type TChangeWorkItemStateListProps = {
|
||||||
|
projectId: string | null;
|
||||||
|
currentStateId: string | null;
|
||||||
|
handleStateChange: (stateId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeWorkItemStateList = observer((props: TChangeWorkItemStateListProps) => {
|
||||||
|
const { projectId, currentStateId, handleStateChange } = props;
|
||||||
|
// store hooks
|
||||||
|
const { getProjectStates } = useProjectState();
|
||||||
|
// derived values
|
||||||
|
const projectStates = getProjectStates(projectId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{projectStates ? (
|
||||||
|
projectStates.length > 0 ? (
|
||||||
|
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" />
|
||||||
|
<p>{state.name}</p>
|
||||||
|
</div>
|
||||||
|
<div>{state.id === currentStateId && <Check className="h-3 w-3" />}</div>
|
||||||
|
</Command.Item>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center">No states found</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./change-state-list";
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { Plus } from "lucide-react";
|
|
||||||
// plane utils
|
|
||||||
import { cn } from "@plane/utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
parentStateId: string;
|
|
||||||
onTransitionAdd?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AddStateTransition = (props: Props) => (
|
|
||||||
<div className={cn("flex w-full px-3 h-6 items-center justify-start gap-2 text-sm bg-custom-background-90")}>
|
|
||||||
<>
|
|
||||||
<Plus className="h-4 w-4" color="#8591AD" />
|
|
||||||
<span className="text-custom-text-400 font-medium"> Add Transition</span>
|
|
||||||
<div className="text-white bg-custom-background-80 font-semibold px-2 rounded-lg">Pro</div>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
export * from "./state-option";
|
export * from "./state-option";
|
||||||
export * from "./state-item-child";
|
|
||||||
export * from "./state-transition-count";
|
|
||||||
export * from "./use-workflow-drag-n-drop";
|
export * from "./use-workflow-drag-n-drop";
|
||||||
export * from "./workflow-disabled-message";
|
export * from "./workflow-disabled-message";
|
||||||
export * from "./workflow-group-tree";
|
export * from "./workflow-group-tree";
|
||||||
|
export * from "./workflow-disabled-overlay";
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { SetStateAction } from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
// Plane
|
|
||||||
import { DISPLAY_WORKFLOW_PRO_CTA } from "@plane/constants";
|
|
||||||
import { IState } from "@plane/types";
|
|
||||||
// components
|
|
||||||
import { StateItemTitle } from "@/components/project-states/state-item-title";
|
|
||||||
// constants
|
|
||||||
//
|
|
||||||
import { AddStateTransition } from "./add-state-transition";
|
|
||||||
|
|
||||||
export type StateItemChildProps = {
|
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
stateCount: number;
|
|
||||||
disabled: boolean;
|
|
||||||
state: IState;
|
|
||||||
setUpdateStateModal: (value: SetStateAction<boolean>) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StateItemChild = observer((props: StateItemChildProps) => {
|
|
||||||
const { workspaceSlug, projectId, stateCount, setUpdateStateModal, disabled, state } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col w-full items-center justify-between">
|
|
||||||
<StateItemTitle
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
setUpdateStateModal={setUpdateStateModal}
|
|
||||||
stateCount={stateCount}
|
|
||||||
disabled={disabled}
|
|
||||||
state={state}
|
|
||||||
/>
|
|
||||||
{DISPLAY_WORKFLOW_PRO_CTA && (
|
|
||||||
<AddStateTransition workspaceSlug={workspaceSlug} projectId={projectId} parentStateId={state.id} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -12,6 +12,7 @@ type Props = {
|
||||||
filterAvailableStateIds: boolean;
|
filterAvailableStateIds: boolean;
|
||||||
selectedValue: string | null | undefined;
|
selectedValue: string | null | undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isForWorkItemCreation?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StateOption = observer((props: Props) => {
|
export const StateOption = observer((props: Props) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { IStateWorkFlow } from "@/plane-web/types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
currentTransitionMap?: IStateWorkFlow;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StateTransitionCount = (props: Props) => <></>;
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { TIssueGroupByOptions } from "@plane/types";
|
import { TIssueGroupByOptions } from "@plane/types";
|
||||||
|
|
||||||
export const useWorkFlowFDragNDrop = (
|
export const useWorkFlowFDragNDrop = (
|
||||||
|
|
@ -6,6 +7,7 @@ export const useWorkFlowFDragNDrop = (
|
||||||
) => ({
|
) => ({
|
||||||
workflowDisabledSource: undefined,
|
workflowDisabledSource: undefined,
|
||||||
isWorkflowDropDisabled: false,
|
isWorkflowDropDisabled: false,
|
||||||
|
getIsWorkflowWorkItemCreationDisabled: (groupId: string, subGroupId?: string) => false,
|
||||||
handleWorkFlowState: (
|
handleWorkFlowState: (
|
||||||
sourceGroupId: string,
|
sourceGroupId: string,
|
||||||
destinationGroupId: string,
|
destinationGroupId: string,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
type Props = {
|
type Props = {
|
||||||
parentStateId: string;
|
parentStateId: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
|
||||||
10
web/ce/components/workflow/workflow-disabled-overlay.tsx
Normal file
10
web/ce/components/workflow/workflow-disabled-overlay.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
|
export type TWorkflowDisabledOverlayProps = {
|
||||||
|
messageContainerRef: React.RefObject<HTMLDivElement>;
|
||||||
|
workflowDisabledSource: string;
|
||||||
|
shouldOverlayBeVisible: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export const WorkFlowDisabledOverlay = observer((props: TWorkflowDisabledOverlayProps) => <></>);
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { TIssueGroupByOptions } from "@plane/types";
|
import { TIssueGroupByOptions } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
||||||
2
web/ce/hooks/use-workspace-issue-properties-extended.tsx
Normal file
2
web/ce/hooks/use-workspace-issue-properties-extended.tsx
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export const useWorkspaceIssuePropertiesExtended = (workspaceSlug: string | string[] | undefined) => {};
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./issue-types";
|
export * from "./issue-types";
|
||||||
export * from "./gantt-chart";
|
export * from "./gantt-chart";
|
||||||
export * from "./state.d";
|
|
||||||
|
|
|
||||||
8
web/ce/types/state.d.ts
vendored
8
web/ce/types/state.d.ts
vendored
|
|
@ -1,8 +0,0 @@
|
||||||
export interface IStateTransition {
|
|
||||||
transition_state_id: string;
|
|
||||||
actors: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IStateWorkFlow {
|
|
||||||
[transitionId: string]: IStateTransition;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Command } from "cmdk";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// hooks
|
// plane imports
|
||||||
import { Check } from "lucide-react";
|
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
import { Spinner, StateGroupIcon } from "@plane/ui";
|
// store hooks
|
||||||
import { useProjectState, useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// ui
|
// plane web imports
|
||||||
// icons
|
import { ChangeWorkItemStateList } from "@/plane-web/components/command-palette/actions/work-item-actions";
|
||||||
// types
|
|
||||||
|
|
||||||
type Props = { closePalette: () => void; issue: TIssue };
|
type Props = { closePalette: () => void; issue: TIssue };
|
||||||
|
|
||||||
|
|
@ -20,10 +17,9 @@ export const ChangeIssueState: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { updateIssue } = useIssueDetail();
|
const { updateIssue } = useIssueDetail();
|
||||||
const { getProjectStates } = useProjectState();
|
|
||||||
// derived values
|
// derived values
|
||||||
const projectId = issue?.project_id;
|
const projectId = issue?.project_id;
|
||||||
const projectStates = getProjectStates(projectId);
|
const currentStateId = issue?.state_id;
|
||||||
|
|
||||||
const submitChanges = async (formData: Partial<TIssue>) => {
|
const submitChanges = async (formData: Partial<TIssue>) => {
|
||||||
if (!workspaceSlug || !projectId || !issue) return;
|
if (!workspaceSlug || !projectId || !issue) return;
|
||||||
|
|
@ -40,24 +36,10 @@ export const ChangeIssueState: React.FC<Props> = observer((props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ChangeWorkItemStateList
|
||||||
{projectStates ? (
|
projectId={projectId}
|
||||||
projectStates.length > 0 ? (
|
currentStateId={currentStateId}
|
||||||
projectStates.map((state) => (
|
handleStateChange={handleIssueState}
|
||||||
<Command.Item key={state.id} onSelect={() => handleIssueState(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" />
|
|
||||||
<p>{state.name}</p>
|
|
||||||
</div>
|
|
||||||
<div>{state.id === issue.state_id && <Check className="h-3 w-3" />}</div>
|
|
||||||
</Command.Item>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="text-center">No states found</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Spinner />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,17 @@ export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
|
||||||
if (!activity) return <></>;
|
if (!activity) return <></>;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative flex items-center gap-2 text-xs ${
|
className={`relative flex items-start gap-2 text-xs ${
|
||||||
ends === "top" ? `pb-3` : ends === "bottom" ? `pt-3` : `py-3`
|
ends === "top" ? `pb-3` : ends === "bottom" ? `pt-3` : `py-3`
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0 ring-6 w-7 h-7 rounded-full overflow-hidden flex justify-center items-start mt-0.5 z-[4] text-custom-text-200">
|
<div className="flex-shrink-0 ring-6 w-7 h-7 rounded-full overflow-hidden flex justify-center items-start mt-0.5 z-[4] text-custom-text-200">
|
||||||
{icon ? icon : <Network className="w-3.5 h-3.5" />}
|
{icon ? icon : <Network className="w-3.5 h-3.5" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full truncate text-custom-text-200">
|
<div className="w-full text-custom-text-200">
|
||||||
|
<div className="line-clamp-2">
|
||||||
<User activity={activity} customUserName={customUserName} /> {children}
|
<User activity={activity} customUserName={customUserName} /> {children}
|
||||||
|
</div>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ type Props = TDropdownProps & {
|
||||||
renderByDefault?: boolean;
|
renderByDefault?: boolean;
|
||||||
stateIds?: string[];
|
stateIds?: string[];
|
||||||
filterAvailableStateIds?: boolean;
|
filterAvailableStateIds?: boolean;
|
||||||
|
isForWorkItemCreation?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StateDropdown: React.FC<Props> = observer((props) => {
|
export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||||
|
|
@ -59,6 +60,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||||
renderByDefault = true,
|
renderByDefault = true,
|
||||||
stateIds,
|
stateIds,
|
||||||
filterAvailableStateIds = true,
|
filterAvailableStateIds = true,
|
||||||
|
isForWorkItemCreation = false,
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
|
@ -98,7 +100,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||||
content: (
|
content: (
|
||||||
<div className="flex items-center gap-2">
|
<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="h-3 w-3 flex-shrink-0" />
|
||||||
<span className="flex-grow truncate">{state?.name}</span>
|
<span className="flex-grow truncate text-left">{state?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
@ -182,7 +184,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||||
<span className="flex-grow truncate">{selectedState?.name ?? t("state")}</span>
|
<span className="flex-grow truncate text-left">{selectedState?.name ?? t("state")}</span>
|
||||||
)}
|
)}
|
||||||
{dropdownArrow && (
|
{dropdownArrow && (
|
||||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
||||||
|
|
@ -239,6 +241,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||||
filterAvailableStateIds={filterAvailableStateIds}
|
filterAvailableStateIds={filterAvailableStateIds}
|
||||||
selectedValue={value}
|
selectedValue={value}
|
||||||
className="flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5"
|
className="flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5"
|
||||||
|
isForWorkItemCreation={isForWorkItemCreation}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
|
import { useRef } from "react";
|
||||||
import { AlertCircle } from "lucide-react";
|
import { AlertCircle } from "lucide-react";
|
||||||
// Plane
|
// plane imports
|
||||||
import { ISSUE_ORDER_BY_OPTIONS } from "@plane/constants";
|
import { ISSUE_ORDER_BY_OPTIONS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TIssueOrderByOptions } from "@plane/types";
|
import { TIssueOrderByOptions } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// Plane-web
|
// plane web imports
|
||||||
import { WorkFlowDisabledMessage } from "@/plane-web/components/workflow";
|
import { WorkFlowDisabledOverlay } from "@/plane-web/components/workflow";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dragColumnOrientation: "justify-start" | "justify-center" | "justify-end";
|
dragColumnOrientation: "justify-start" | "justify-center" | "justify-end";
|
||||||
|
|
@ -30,9 +31,10 @@ export const GroupDragOverlay = (props: Props) => {
|
||||||
isDraggingOverColumn,
|
isDraggingOverColumn,
|
||||||
isEpic = false,
|
isEpic = false,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
// refs
|
||||||
|
const messageContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
|
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
|
||||||
const readableOrderBy = t(
|
const readableOrderBy = t(
|
||||||
|
|
@ -41,27 +43,28 @@ export const GroupDragOverlay = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={messageContainerRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
`absolute top-0 left-0 h-full w-full items-center text-sm font-medium text-custom-text-300 rounded bg-custom-background-overlay ${dragColumnOrientation}`,
|
`absolute top-0 left-0 h-full w-full items-center text-sm font-medium text-custom-text-300 rounded bg-custom-background-80/85 ${dragColumnOrientation}`,
|
||||||
{
|
{
|
||||||
"flex flex-col border-[1px] border-custom-border-300 z-[2]": shouldOverlayBeVisible,
|
"flex flex-col border-[1px] border-custom-border-300 z-[2]": shouldOverlayBeVisible,
|
||||||
|
"bg-red-200/60": workflowDisabledSource && isDropDisabled,
|
||||||
},
|
},
|
||||||
{ hidden: !shouldOverlayBeVisible }
|
{ hidden: !shouldOverlayBeVisible }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{workflowDisabledSource ? (
|
{workflowDisabledSource ? (
|
||||||
<WorkFlowDisabledMessage parentStateId={workflowDisabledSource} className="my-2" />
|
<WorkFlowDisabledOverlay
|
||||||
|
messageContainerRef={messageContainerRef}
|
||||||
|
workflowDisabledSource={workflowDisabledSource}
|
||||||
|
shouldOverlayBeVisible={shouldOverlayBeVisible}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn("p-3 my-8 flex flex-col rounded items-center", {
|
||||||
"p-3 my-8 flex flex-col rounded items-center",
|
|
||||||
{
|
|
||||||
"text-custom-text-200": shouldOverlayBeVisible,
|
"text-custom-text-200": shouldOverlayBeVisible,
|
||||||
},
|
|
||||||
{
|
|
||||||
"text-custom-text-error": isDropDisabled,
|
"text-custom-text-error": isDropDisabled,
|
||||||
}
|
})}
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{dropErrorMessage ? (
|
{dropErrorMessage ? (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { useKanbanView } from "@/hooks/store";
|
||||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
// types
|
// types
|
||||||
// parent components
|
// parent components
|
||||||
|
import { useWorkFlowFDragNDrop } from "@/plane-web/components/workflow";
|
||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
import { TRenderQuickActions } from "../list/list-view-types";
|
||||||
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation, getApproximateCardHeight } from "../utils";
|
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation, getApproximateCardHeight } from "../utils";
|
||||||
// components
|
// components
|
||||||
|
|
@ -98,6 +99,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
const issueKanBanView = useKanbanView();
|
const issueKanBanView = useKanbanView();
|
||||||
// derived values
|
// derived values
|
||||||
const isDragDisabled = !issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by);
|
const isDragDisabled = !issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by);
|
||||||
|
|
||||||
|
const { getIsWorkflowWorkItemCreationDisabled } = useWorkFlowFDragNDrop(group_by, sub_group_by);
|
||||||
|
|
||||||
const list = getGroupByColumns({
|
const list = getGroupByColumns({
|
||||||
groupBy: group_by as GroupByColumnTypes,
|
groupBy: group_by as GroupByColumnTypes,
|
||||||
includeNone: true,
|
includeNone: true,
|
||||||
|
|
@ -167,7 +171,11 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
title={subList.name}
|
title={subList.name}
|
||||||
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
||||||
issuePayload={subList.payload}
|
issuePayload={subList.payload}
|
||||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
disableIssueCreation={
|
||||||
|
disableIssueCreation ||
|
||||||
|
isGroupByCreatedBy ||
|
||||||
|
getIsWorkflowWorkItemCreationDisabled(subList.id, sub_group_id)
|
||||||
|
}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
collapsedGroups={collapsedGroups}
|
collapsedGroups={collapsedGroups}
|
||||||
handleCollapsedGroups={handleCollapsedGroups}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,8 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
);
|
);
|
||||||
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
||||||
|
|
||||||
const { workflowDisabledSource, isWorkflowDropDisabled, handleWorkFlowState } = useWorkFlowFDragNDrop(
|
const { workflowDisabledSource, isWorkflowDropDisabled, handleWorkFlowState, getIsWorkflowWorkItemCreationDisabled } =
|
||||||
group_by,
|
useWorkFlowFDragNDrop(group_by, sub_group_by);
|
||||||
sub_group_by
|
|
||||||
);
|
|
||||||
|
|
||||||
// Enable Kanban Columns as Drop Targets
|
// Enable Kanban Columns as Drop Targets
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -306,7 +304,9 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
|
|
||||||
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
|
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
|
||||||
|
|
||||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
{enableQuickIssueCreate &&
|
||||||
|
!disableIssueCreation &&
|
||||||
|
!getIsWorkflowWorkItemCreationDisabled(groupId, sub_group_id) && (
|
||||||
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
|
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
|
||||||
<QuickAddIssueRoot
|
<QuickAddIssueRoot
|
||||||
layout={EIssueLayoutTypes.KANBAN}
|
layout={EIssueLayoutTypes.KANBAN}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { MutableRefObject } from "react";
|
import { MutableRefObject } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useTranslation } from "@plane/i18n";
|
|
||||||
import {
|
import {
|
||||||
GroupByColumnTypes,
|
GroupByColumnTypes,
|
||||||
IGroupByColumn,
|
IGroupByColumn,
|
||||||
|
|
@ -18,6 +17,7 @@ import { Row } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
// components
|
// components
|
||||||
|
import { useWorkFlowFDragNDrop } from "@/plane-web/components/workflow";
|
||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
import { TRenderQuickActions } from "../list/list-view-types";
|
||||||
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation } from "../utils";
|
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation } from "../utils";
|
||||||
import { KanBan } from "./default";
|
import { KanBan } from "./default";
|
||||||
|
|
@ -53,7 +53,10 @@ const visibilitySubGroupByGroupCount = (subGroupIssueCount: number, showEmptyGro
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||||
({ getGroupIssueCount, sub_group_by, group_by, list, collapsedGroups, handleCollapsedGroups, showEmptyGroup }) => (
|
({ getGroupIssueCount, sub_group_by, group_by, list, collapsedGroups, handleCollapsedGroups, showEmptyGroup }) => {
|
||||||
|
const { getIsWorkflowWorkItemCreationDisabled } = useWorkFlowFDragNDrop(group_by, sub_group_by);
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="relative flex h-max min-h-full w-full items-center gap-4">
|
<div className="relative flex h-max min-h-full w-full items-center gap-4">
|
||||||
{list &&
|
{list &&
|
||||||
list.length > 0 &&
|
list.length > 0 &&
|
||||||
|
|
@ -63,7 +66,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||||
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
||||||
|
|
||||||
if (subGroupByVisibilityToggle === false) return <></>;
|
if (subGroupByVisibilityToggle === false) return <></>;
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
return (
|
||||||
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
||||||
<HeaderGroupByCard
|
<HeaderGroupByCard
|
||||||
|
|
@ -76,12 +79,14 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||||
collapsedGroups={collapsedGroups}
|
collapsedGroups={collapsedGroups}
|
||||||
handleCollapsedGroups={handleCollapsedGroups}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
issuePayload={_list.payload}
|
issuePayload={_list.payload}
|
||||||
/>{" "}
|
disableIssueCreation={getIsWorkflowWorkItemCreationDisabled(_list.id)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||||
|
|
@ -156,7 +161,6 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
{list &&
|
{list &&
|
||||||
list.length > 0 &&
|
list.length > 0 &&
|
||||||
list.map((_list: IGroupByColumn, subGroupIndex) => {
|
list.map((_list: IGroupByColumn, subGroupIndex) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const issueCount = getGroupIssueCount(undefined, _list.id, true) ?? 0;
|
const issueCount = getGroupIssueCount(undefined, _list.id, true) ?? 0;
|
||||||
const subGroupByVisibilityToggle = visibilitySubGroupBy(_list, issueCount);
|
const subGroupByVisibilityToggle = visibilitySubGroupBy(_list, issueCount);
|
||||||
if (subGroupByVisibilityToggle.showGroup === false) return <></>;
|
if (subGroupByVisibilityToggle.showGroup === false) return <></>;
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,9 @@ export const ListGroup = observer((props: Props) => {
|
||||||
|
|
||||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const { workflowDisabledSource, isWorkflowDropDisabled, handleWorkFlowState } = useWorkFlowFDragNDrop(group_by);
|
const { workflowDisabledSource, isWorkflowDropDisabled, handleWorkFlowState, getIsWorkflowWorkItemCreationDisabled } =
|
||||||
|
useWorkFlowFDragNDrop(group_by);
|
||||||
|
const isWorkflowIssueCreationDisabled = getIsWorkflowWorkItemCreationDisabled(group.id);
|
||||||
|
|
||||||
const groupIssueCount = getGroupIssueCount(group.id, undefined, false) ?? 0;
|
const groupIssueCount = getGroupIssueCount(group.id, undefined, false) ?? 0;
|
||||||
const nextPageResults = getPaginationData(group.id, undefined)?.nextPageResults;
|
const nextPageResults = getPaginationData(group.id, undefined)?.nextPageResults;
|
||||||
|
|
@ -267,7 +269,9 @@ export const ListGroup = observer((props: Props) => {
|
||||||
count={groupIssueCount}
|
count={groupIssueCount}
|
||||||
issuePayload={group.payload}
|
issuePayload={group.payload}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy || isCompletedCycle}
|
disableIssueCreation={
|
||||||
|
disableIssueCreation || isGroupByCreatedBy || isCompletedCycle || isWorkflowIssueCreationDisabled
|
||||||
|
}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
selectionHelpers={selectionHelpers}
|
selectionHelpers={selectionHelpers}
|
||||||
handleCollapsedGroups={handleCollapsedGroups}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
|
|
@ -305,7 +309,11 @@ export const ListGroup = observer((props: Props) => {
|
||||||
|
|
||||||
{shouldLoadMore && (group_by ? <>{loadMore}</> : <ListLoaderItemRow ref={setIntersectionElement} />)}
|
{shouldLoadMore && (group_by ? <>{loadMore}</> : <ListLoaderItemRow ref={setIntersectionElement} />)}
|
||||||
|
|
||||||
{enableIssueQuickAdd && !disableIssueCreation && !isGroupByCreatedBy && !isCompletedCycle && (
|
{enableIssueQuickAdd &&
|
||||||
|
!disableIssueCreation &&
|
||||||
|
!isGroupByCreatedBy &&
|
||||||
|
!isCompletedCycle &&
|
||||||
|
!isWorkflowIssueCreationDisabled && (
|
||||||
<div className="sticky bottom-0 z-[1] w-full flex-shrink-0">
|
<div className="sticky bottom-0 z-[1] w-full flex-shrink-0">
|
||||||
<QuickAddIssueRoot
|
<QuickAddIssueRoot
|
||||||
layout={EIssueLayoutTypes.LIST}
|
layout={EIssueLayoutTypes.LIST}
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
||||||
projectId={projectId ?? undefined}
|
projectId={projectId ?? undefined}
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
tabIndex={getIndex("state_id")}
|
tabIndex={getIndex("state_id")}
|
||||||
|
isForWorkItemCreation={!id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export const StateMarksAsDefault: FC<TStateMarksAsDefault> = observer((props) =>
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm whitespace-nowrap transition-colors",
|
"text-xs whitespace-nowrap transition-colors",
|
||||||
isDefault ? "text-custom-text-300" : "text-custom-text-200 hover:text-custom-text-100"
|
isDefault ? "text-custom-text-300" : "text-custom-text-200 hover:text-custom-text-100"
|
||||||
)}
|
)}
|
||||||
disabled={isDefault || isLoading}
|
disabled={isDefault || isLoading}
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,11 @@ type TProjectState = {
|
||||||
export const ProjectStateRoot: FC<TProjectState> = observer((props) => {
|
export const ProjectStateRoot: FC<TProjectState> = observer((props) => {
|
||||||
const { workspaceSlug, projectId } = props;
|
const { workspaceSlug, projectId } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { groupedProjectStates, fetchProjectStates, fetchProjectStateTransitions } = useProjectState();
|
const { groupedProjectStates, fetchProjectStates } = useProjectState();
|
||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
|
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null,
|
||||||
? () => {
|
|
||||||
fetchProjectStates(workspaceSlug.toString(), projectId.toString());
|
|
||||||
fetchProjectStateTransitions(workspaceSlug.toString(), projectId.toString());
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
import { SetStateAction } from "react";
|
import { SetStateAction } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { GripVertical, Pencil } from "lucide-react";
|
import { GripVertical, Pencil } from "lucide-react";
|
||||||
// Plane
|
// plane imports
|
||||||
import { IState } from "@plane/types";
|
import { IState } from "@plane/types";
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// Plane-web
|
// local imports
|
||||||
import { StateTransitionCount } from "@/plane-web/components/workflow";
|
|
||||||
import { IStateWorkFlow } from "@/plane-web/types";
|
|
||||||
//
|
|
||||||
import { StateDelete, StateMarksAsDefault } from "./options";
|
import { StateDelete, StateMarksAsDefault } from "./options";
|
||||||
|
|
||||||
export type StateItemTitleProps = {
|
export type StateItemTitleProps = {
|
||||||
|
|
@ -17,31 +14,37 @@ export type StateItemTitleProps = {
|
||||||
stateCount: number;
|
stateCount: number;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
state: IState;
|
state: IState;
|
||||||
currentTransitionMap?: IStateWorkFlow;
|
shouldShowDescription?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StateItemTitle = observer((props: StateItemTitleProps) => {
|
export const StateItemTitle = observer((props: StateItemTitleProps) => {
|
||||||
const { workspaceSlug, projectId, stateCount, setUpdateStateModal, disabled, state, currentTransitionMap } = props;
|
const {
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
stateCount,
|
||||||
|
setUpdateStateModal,
|
||||||
|
disabled,
|
||||||
|
state,
|
||||||
|
shouldShowDescription = true,
|
||||||
|
} = props;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 w-full justify-between">
|
<div className="flex items-center gap-2 w-full justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1 px-1">
|
||||||
{/* draggable indicator */}
|
{/* draggable indicator */}
|
||||||
{!disabled && stateCount != 1 && (
|
{!disabled && stateCount != 1 && (
|
||||||
<div className="flex-shrink-0 w-3 h-3 rounded-sm absolute left-0 hidden group-hover:flex justify-center items-center transition-colors bg-custom-background-90 cursor-pointer text-custom-text-200 hover:text-custom-text-100">
|
<div className="flex-shrink-0 w-3 h-3 rounded-sm absolute -left-1.5 hidden group-hover:flex justify-center items-center transition-colors bg-custom-background-90 cursor-pointer text-custom-text-200 hover:text-custom-text-100">
|
||||||
<GripVertical className="w-3 h-3" />
|
<GripVertical className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* state icon */}
|
{/* state icon */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
|
<StateGroupIcon stateGroup={state.group} color={state.color} className={"size-3.5"} />
|
||||||
</div>
|
</div>
|
||||||
{/* state title and description */}
|
{/* state title and description */}
|
||||||
<div className="text-sm px-2 min-h-5">
|
<div className="text-sm px-2 min-h-5">
|
||||||
<h6 className="text-sm font-medium">{state.name}</h6>
|
<h6 className="text-sm font-medium">{state.name}</h6>
|
||||||
<p className="text-xs text-custom-text-200">{state.description}</p>
|
{shouldShowDescription && <p className="text-xs text-custom-text-200">{state.description}</p>}
|
||||||
</div>
|
</div>
|
||||||
{/* Transition count */}
|
|
||||||
<StateTransitionCount currentTransitionMap={currentTransitionMap} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,12 @@ import { TDraggableData } from "@plane/constants";
|
||||||
import { IState, TStateGroups } from "@plane/types";
|
import { IState, TStateGroups } from "@plane/types";
|
||||||
import { DropIndicator } from "@plane/ui";
|
import { DropIndicator } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { StateUpdate } from "@/components/project-states";
|
import { StateItemTitle, StateUpdate } from "@/components/project-states";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getCurrentStateSequence } from "@/helpers/state.helper";
|
import { getCurrentStateSequence } from "@/helpers/state.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectState } from "@/hooks/store";
|
import { useProjectState } from "@/hooks/store";
|
||||||
// Plane-web
|
|
||||||
import { StateItemChild } from "@/plane-web/components/workflow";
|
|
||||||
|
|
||||||
type TStateItem = {
|
type TStateItem = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -133,7 +131,7 @@ export const StateItem: FC<TStateItem> = observer((props) => {
|
||||||
totalStates === 1 ? `cursor-auto` : `cursor-grab`
|
totalStates === 1 ? `cursor-auto` : `cursor-grab`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<StateItemChild
|
<StateItemTitle
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
setUpdateStateModal={setUpdateStateModal}
|
setUpdateStateModal={setUpdateStateModal}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useCycle, useProjectEstimates, useLabel, useModule, useProjectState } from "./store";
|
// plane web imports
|
||||||
|
import { useWorkspaceIssuePropertiesExtended } from "@/plane-web/hooks/use-workspace-issue-properties-extended";
|
||||||
|
// plane imports
|
||||||
|
import { useCycle, useProjectEstimates, useLabel, useModule } from "./store";
|
||||||
|
|
||||||
export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | undefined) => {
|
export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | undefined) => {
|
||||||
const { fetchWorkspaceLabels } = useLabel();
|
const { fetchWorkspaceLabels } = useLabel();
|
||||||
|
|
@ -37,4 +40,7 @@ export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | u
|
||||||
workspaceSlug ? () => getWorkspaceEstimates(workspaceSlug.toString()) : null,
|
workspaceSlug ? () => getWorkspaceEstimates(workspaceSlug.toString()) : null,
|
||||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// fetch extended issue properties
|
||||||
|
useWorkspaceIssuePropertiesExtended(workspaceSlug);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
project: { fetchProjectMembers },
|
project: { fetchProjectMembers },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
const { fetchProjectStates, fetchProjectStateTransitions } = useProjectState();
|
const { fetchProjectStates } = useProjectState();
|
||||||
const { fetchProjectLabels } = useLabel();
|
const { fetchProjectLabels } = useLabel();
|
||||||
const { getProjectEstimates } = useProjectEstimates();
|
const { getProjectEstimates } = useProjectEstimates();
|
||||||
|
|
||||||
|
|
@ -118,12 +118,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
||||||
// fetching project states
|
// fetching project states
|
||||||
useSWR(
|
useSWR(
|
||||||
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
|
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null,
|
||||||
? () => {
|
|
||||||
fetchProjectStates(workspaceSlug.toString(), projectId.toString());
|
|
||||||
fetchProjectStateTransitions(workspaceSlug.toString(), projectId.toString());
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
// fetching project estimates
|
// fetching project estimates
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { computedFn } from "mobx-utils";
|
||||||
import { STATE_GROUPS } from "@plane/constants";
|
import { STATE_GROUPS } from "@plane/constants";
|
||||||
import { IState } from "@plane/types";
|
import { IState } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { convertStringArrayToBooleanObject } from "@/helpers/array.helper";
|
|
||||||
import { sortStates } from "@/helpers/state.helper";
|
import { sortStates } from "@/helpers/state.helper";
|
||||||
// plane web
|
// plane web
|
||||||
import { syncIssuesWithDeletedStates } from "@/local-db/utils/load-workspace";
|
import { syncIssuesWithDeletedStates } from "@/local-db/utils/load-workspace";
|
||||||
|
|
@ -25,10 +24,6 @@ export interface IStateStore {
|
||||||
// computed actions
|
// computed actions
|
||||||
getStateById: (stateId: string | null | undefined) => IState | undefined;
|
getStateById: (stateId: string | null | undefined) => IState | undefined;
|
||||||
getProjectStates: (projectId: string | null | undefined) => IState[] | undefined;
|
getProjectStates: (projectId: string | null | undefined) => IState[] | undefined;
|
||||||
getAvailableProjectStateIdMap: (
|
|
||||||
projectId: string | null | undefined,
|
|
||||||
currStateId: string | null | undefined
|
|
||||||
) => { [key: string]: boolean };
|
|
||||||
// fetch actions
|
// fetch actions
|
||||||
fetchProjectStates: (workspaceSlug: string, projectId: string) => Promise<IState[]>;
|
fetchProjectStates: (workspaceSlug: string, projectId: string) => Promise<IState[]>;
|
||||||
fetchWorkspaceStates: (workspaceSlug: string) => Promise<IState[]>;
|
fetchWorkspaceStates: (workspaceSlug: string) => Promise<IState[]>;
|
||||||
|
|
@ -48,8 +43,6 @@ export interface IStateStore {
|
||||||
stateId: string,
|
stateId: string,
|
||||||
payload: Partial<IState>
|
payload: Partial<IState>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
//Dummy method
|
|
||||||
fetchProjectStateTransitions: (workspaceSlug: string, projectId: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StateStore implements IStateStore {
|
export class StateStore implements IStateStore {
|
||||||
|
|
@ -143,20 +136,6 @@ export class StateStore implements IStateStore {
|
||||||
return sortStates(Object.values(this.stateMap).filter((state) => state.project_id === projectId));
|
return sortStates(Object.values(this.stateMap).filter((state) => state.project_id === projectId));
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object linking state permissions as boolean values
|
|
||||||
* @param projectId
|
|
||||||
*/
|
|
||||||
getAvailableProjectStateIdMap = computedFn(
|
|
||||||
(projectId: string | null | undefined, currStateId: string | null | undefined) => {
|
|
||||||
const projectStates = this.getProjectStates(projectId);
|
|
||||||
|
|
||||||
if (!projectStates) return {};
|
|
||||||
|
|
||||||
return convertStringArrayToBooleanObject(projectStates.map((projectState) => projectState.id));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fetches the stateMap of a project
|
* fetches the stateMap of a project
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
|
|
@ -292,14 +271,11 @@ export class StateStore implements IStateStore {
|
||||||
});
|
});
|
||||||
// updating using api
|
// updating using api
|
||||||
await this.stateService.patchState(workspaceSlug, projectId, stateId, payload);
|
await this.stateService.patchState(workspaceSlug, projectId, stateId, payload);
|
||||||
} catch (err) {
|
} catch {
|
||||||
// reverting back to old state group if api fails
|
// reverting back to old state group if api fails
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.stateMap = originalStates;
|
this.stateMap = originalStates;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dummy method
|
|
||||||
fetchProjectStateTransitions = (workspaceSlug: string, projectId: string) => {};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./issue-types";
|
export * from "./issue-types";
|
||||||
export * from "ce/types/state.d";
|
|
||||||
|
|
|
||||||
|
|
@ -37,3 +37,13 @@
|
||||||
[cmdk-item][aria-selected="true"] {
|
[cmdk-item][aria-selected="true"] {
|
||||||
background-color: rgba(var(--color-background-80));
|
background-color: rgba(var(--color-background-80));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[cmdk-item][aria-disabled="true"], [cmdk-item][data-disabled="true"] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-item][aria-disabled="true"]:hover, [cmdk-item][data-disabled="true"]:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue