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",
|
||||
"installing": "Installing",
|
||||
"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",
|
||||
"categories": "Categories",
|
||||
"saving": "Saving",
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@
|
|||
"activity": "Actividad",
|
||||
"appearance": "Apariencia",
|
||||
"notifications": "Notificaciones",
|
||||
"connections": "Conexiones",
|
||||
"workspaces": "Espacios de trabajo",
|
||||
"create_workspace": "Crear espacio de trabajo",
|
||||
"invitations": "Invitaciones",
|
||||
|
|
@ -291,6 +292,7 @@
|
|||
"workspace_logo": "Logo del espacio de trabajo",
|
||||
"new_issue": "Nuevo elemento de trabajo",
|
||||
"your_work": "Tu trabajo",
|
||||
"workspace_dashboards": "Paneles de control",
|
||||
"drafts": "Borradores",
|
||||
"projects": "Proyectos",
|
||||
"views": "Vistas",
|
||||
|
|
@ -845,6 +847,16 @@
|
|||
"disconnecting": "Desconectando",
|
||||
"installing": "Instalando",
|
||||
"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",
|
||||
"categories": "Categorías",
|
||||
"saving": "Guardando",
|
||||
|
|
|
|||
|
|
@ -845,6 +845,16 @@
|
|||
"disconnecting": "Déconnexion",
|
||||
"installing": "Installation",
|
||||
"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",
|
||||
"categories": "Catégories",
|
||||
"saving": "Enregistrement",
|
||||
|
|
|
|||
|
|
@ -845,6 +845,16 @@
|
|||
"disconnecting": "切断中",
|
||||
"installing": "インストール中",
|
||||
"install": "インストール",
|
||||
"reset": "リセット",
|
||||
"live": "ライブ",
|
||||
"change_history": "変更履歴",
|
||||
"coming_soon": "近日公開",
|
||||
"members": "メンバー",
|
||||
"you": "あなた",
|
||||
"upgrade_cta": {
|
||||
"higher_subscription": "高いサブスクリプションにアップグレード",
|
||||
"talk_to_sales": "セールスに連絡"
|
||||
},
|
||||
"category": "カテゴリー",
|
||||
"categories": "カテゴリーズ",
|
||||
"saving": "セービング",
|
||||
|
|
|
|||
|
|
@ -845,6 +845,16 @@
|
|||
"disconnecting": "正在断开连接",
|
||||
"installing": "正在安装",
|
||||
"install": "安装",
|
||||
"reset": "重置",
|
||||
"live": "实时",
|
||||
"change_history": "变更历史",
|
||||
"coming_soon": "即将推出",
|
||||
"members": "成员",
|
||||
"you": "你",
|
||||
"upgrade_cta": {
|
||||
"higher_subscription": "升级到更高订阅",
|
||||
"talk_to_sales": "联系销售"
|
||||
},
|
||||
"category": "类别",
|
||||
"categories": "类别",
|
||||
"saving": "保存中",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
// helpers
|
||||
import { cn } from "../../helpers";
|
||||
|
||||
|
|
@ -23,7 +23,6 @@ export type TPosition =
|
|||
interface ITooltipProps {
|
||||
tooltipHeading?: string;
|
||||
tooltipContent: string | React.ReactNode;
|
||||
jsxContent?: string | React.ReactNode;
|
||||
position?: TPosition;
|
||||
children: JSX.Element;
|
||||
disabled?: boolean;
|
||||
|
|
@ -39,14 +38,13 @@ export const Tooltip: React.FC<ITooltipProps> = ({
|
|||
tooltipContent,
|
||||
position = "top",
|
||||
children,
|
||||
jsxContent,
|
||||
disabled = false,
|
||||
className = "",
|
||||
openDelay = 200,
|
||||
closeDelay,
|
||||
isMobile = false,
|
||||
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 [shouldRender, setShouldRender] = useState(renderByDefault);
|
||||
|
|
@ -81,9 +79,6 @@ export const Tooltip: React.FC<ITooltipProps> = ({
|
|||
hoverOpenDelay={openDelay}
|
||||
hoverCloseDelay={closeDelay}
|
||||
content={
|
||||
jsxContent ? (
|
||||
<>{jsxContent}</>
|
||||
) : (
|
||||
<div
|
||||
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",
|
||||
|
|
@ -96,7 +91,6 @@ export const Tooltip: React.FC<ITooltipProps> = ({
|
|||
{tooltipHeading && <h5 className="font-medium text-custom-text-100">{tooltipHeading}</h5>}
|
||||
{tooltipContent}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
position={position}
|
||||
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-item-child";
|
||||
export * from "./state-transition-count";
|
||||
export * from "./use-workflow-drag-n-drop";
|
||||
export * from "./workflow-disabled-message";
|
||||
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;
|
||||
selectedValue: string | null | undefined;
|
||||
className?: string;
|
||||
isForWorkItemCreation?: boolean;
|
||||
};
|
||||
|
||||
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";
|
||||
|
||||
export const useWorkFlowFDragNDrop = (
|
||||
|
|
@ -6,6 +7,7 @@ export const useWorkFlowFDragNDrop = (
|
|||
) => ({
|
||||
workflowDisabledSource: undefined,
|
||||
isWorkflowDropDisabled: false,
|
||||
getIsWorkflowWorkItemCreationDisabled: (groupId: string, subGroupId?: string) => false,
|
||||
handleWorkFlowState: (
|
||||
sourceGroupId: string,
|
||||
destinationGroupId: string,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
type Props = {
|
||||
parentStateId: 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";
|
||||
|
||||
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 "./issue-types";
|
||||
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";
|
||||
|
||||
import { Command } from "cmdk";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// hooks
|
||||
import { Check } from "lucide-react";
|
||||
// plane imports
|
||||
import { TIssue } from "@plane/types";
|
||||
import { Spinner, StateGroupIcon } from "@plane/ui";
|
||||
import { useProjectState, useIssueDetail } from "@/hooks/store";
|
||||
// ui
|
||||
// icons
|
||||
// types
|
||||
// store hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// plane web imports
|
||||
import { ChangeWorkItemStateList } from "@/plane-web/components/command-palette/actions/work-item-actions";
|
||||
|
||||
type Props = { closePalette: () => void; issue: TIssue };
|
||||
|
||||
|
|
@ -20,10 +17,9 @@ export const ChangeIssueState: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { updateIssue } = useIssueDetail();
|
||||
const { getProjectStates } = useProjectState();
|
||||
// derived values
|
||||
const projectId = issue?.project_id;
|
||||
const projectStates = getProjectStates(projectId);
|
||||
const currentStateId = issue?.state_id;
|
||||
|
||||
const submitChanges = async (formData: Partial<TIssue>) => {
|
||||
if (!workspaceSlug || !projectId || !issue) return;
|
||||
|
|
@ -40,24 +36,10 @@ export const ChangeIssueState: React.FC<Props> = observer((props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{projectStates ? (
|
||||
projectStates.length > 0 ? (
|
||||
projectStates.map((state) => (
|
||||
<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 />
|
||||
)}
|
||||
</>
|
||||
<ChangeWorkItemStateList
|
||||
projectId={projectId}
|
||||
currentStateId={currentStateId}
|
||||
handleStateChange={handleIssueState}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,15 +29,17 @@ export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
|
|||
if (!activity) return <></>;
|
||||
return (
|
||||
<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`
|
||||
}`}
|
||||
>
|
||||
<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" />}
|
||||
</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}
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ type Props = TDropdownProps & {
|
|||
renderByDefault?: boolean;
|
||||
stateIds?: string[];
|
||||
filterAvailableStateIds?: boolean;
|
||||
isForWorkItemCreation?: boolean;
|
||||
};
|
||||
|
||||
export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -59,6 +60,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||
renderByDefault = true,
|
||||
stateIds,
|
||||
filterAvailableStateIds = true,
|
||||
isForWorkItemCreation = false,
|
||||
} = props;
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
|
|
@ -98,7 +100,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
),
|
||||
}));
|
||||
|
|
@ -182,7 +184,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||
/>
|
||||
)}
|
||||
{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 && (
|
||||
<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}
|
||||
selectedValue={value}
|
||||
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";
|
||||
// Plane
|
||||
// plane imports
|
||||
import { ISSUE_ORDER_BY_OPTIONS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TIssueOrderByOptions } from "@plane/types";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// Plane-web
|
||||
import { WorkFlowDisabledMessage } from "@/plane-web/components/workflow";
|
||||
// plane web imports
|
||||
import { WorkFlowDisabledOverlay } from "@/plane-web/components/workflow";
|
||||
|
||||
type Props = {
|
||||
dragColumnOrientation: "justify-start" | "justify-center" | "justify-end";
|
||||
|
|
@ -30,9 +31,10 @@ export const GroupDragOverlay = (props: Props) => {
|
|||
isDraggingOverColumn,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
// refs
|
||||
const messageContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
|
||||
const readableOrderBy = t(
|
||||
|
|
@ -41,27 +43,28 @@ export const GroupDragOverlay = (props: Props) => {
|
|||
|
||||
return (
|
||||
<div
|
||||
ref={messageContainerRef}
|
||||
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,
|
||||
"bg-red-200/60": workflowDisabledSource && isDropDisabled,
|
||||
},
|
||||
{ hidden: !shouldOverlayBeVisible }
|
||||
)}
|
||||
>
|
||||
{workflowDisabledSource ? (
|
||||
<WorkFlowDisabledMessage parentStateId={workflowDisabledSource} className="my-2" />
|
||||
<WorkFlowDisabledOverlay
|
||||
messageContainerRef={messageContainerRef}
|
||||
workflowDisabledSource={workflowDisabledSource}
|
||||
shouldOverlayBeVisible={shouldOverlayBeVisible}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"p-3 my-8 flex flex-col rounded items-center",
|
||||
{
|
||||
className={cn("p-3 my-8 flex flex-col rounded items-center", {
|
||||
"text-custom-text-200": shouldOverlayBeVisible,
|
||||
},
|
||||
{
|
||||
"text-custom-text-error": isDropDisabled,
|
||||
}
|
||||
)}
|
||||
})}
|
||||
>
|
||||
{dropErrorMessage ? (
|
||||
<div className="flex items-center">
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { useKanbanView } from "@/hooks/store";
|
|||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
// types
|
||||
// parent components
|
||||
import { useWorkFlowFDragNDrop } from "@/plane-web/components/workflow";
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation, getApproximateCardHeight } from "../utils";
|
||||
// components
|
||||
|
|
@ -98,6 +99,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
const issueKanBanView = useKanbanView();
|
||||
// derived values
|
||||
const isDragDisabled = !issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by);
|
||||
|
||||
const { getIsWorkflowWorkItemCreationDisabled } = useWorkFlowFDragNDrop(group_by, sub_group_by);
|
||||
|
||||
const list = getGroupByColumns({
|
||||
groupBy: group_by as GroupByColumnTypes,
|
||||
includeNone: true,
|
||||
|
|
@ -167,7 +171,11 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
title={subList.name}
|
||||
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
||||
issuePayload={subList.payload}
|
||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
||||
disableIssueCreation={
|
||||
disableIssueCreation ||
|
||||
isGroupByCreatedBy ||
|
||||
getIsWorkflowWorkItemCreationDisabled(subList.id, sub_group_id)
|
||||
}
|
||||
addIssuesToView={addIssuesToView}
|
||||
collapsedGroups={collapsedGroups}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
|
|
|
|||
|
|
@ -113,10 +113,8 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
|||
);
|
||||
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
||||
|
||||
const { workflowDisabledSource, isWorkflowDropDisabled, handleWorkFlowState } = useWorkFlowFDragNDrop(
|
||||
group_by,
|
||||
sub_group_by
|
||||
);
|
||||
const { workflowDisabledSource, isWorkflowDropDisabled, handleWorkFlowState, getIsWorkflowWorkItemCreationDisabled } =
|
||||
useWorkFlowFDragNDrop(group_by, sub_group_by);
|
||||
|
||||
// Enable Kanban Columns as Drop Targets
|
||||
useEffect(() => {
|
||||
|
|
@ -306,7 +304,9 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
|||
|
||||
{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">
|
||||
<QuickAddIssueRoot
|
||||
layout={EIssueLayoutTypes.KANBAN}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { MutableRefObject } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import {
|
||||
GroupByColumnTypes,
|
||||
IGroupByColumn,
|
||||
|
|
@ -18,6 +17,7 @@ import { Row } from "@plane/ui";
|
|||
// hooks
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
// components
|
||||
import { useWorkFlowFDragNDrop } from "@/plane-web/components/workflow";
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation } from "../utils";
|
||||
import { KanBan } from "./default";
|
||||
|
|
@ -53,7 +53,10 @@ const visibilitySubGroupByGroupCount = (subGroupIssueCount: number, showEmptyGro
|
|||
};
|
||||
|
||||
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">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
|
|
@ -63,7 +66,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
|||
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
||||
|
||||
if (subGroupByVisibilityToggle === false) return <></>;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
||||
<HeaderGroupByCard
|
||||
|
|
@ -76,12 +79,14 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
|||
collapsedGroups={collapsedGroups}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
issuePayload={_list.payload}
|
||||
/>{" "}
|
||||
disableIssueCreation={getIsWorkflowWorkItemCreationDisabled(_list.id)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
|
|
@ -156,7 +161,6 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: IGroupByColumn, subGroupIndex) => {
|
||||
const { t } = useTranslation();
|
||||
const issueCount = getGroupIssueCount(undefined, _list.id, true) ?? 0;
|
||||
const subGroupByVisibilityToggle = visibilitySubGroupBy(_list, issueCount);
|
||||
if (subGroupByVisibilityToggle.showGroup === false) return <></>;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@ export const ListGroup = observer((props: Props) => {
|
|||
|
||||
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 nextPageResults = getPaginationData(group.id, undefined)?.nextPageResults;
|
||||
|
|
@ -267,7 +269,9 @@ export const ListGroup = observer((props: Props) => {
|
|||
count={groupIssueCount}
|
||||
issuePayload={group.payload}
|
||||
canEditProperties={canEditProperties}
|
||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy || isCompletedCycle}
|
||||
disableIssueCreation={
|
||||
disableIssueCreation || isGroupByCreatedBy || isCompletedCycle || isWorkflowIssueCreationDisabled
|
||||
}
|
||||
addIssuesToView={addIssuesToView}
|
||||
selectionHelpers={selectionHelpers}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
|
|
@ -305,7 +309,11 @@ export const ListGroup = observer((props: Props) => {
|
|||
|
||||
{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">
|
||||
<QuickAddIssueRoot
|
||||
layout={EIssueLayoutTypes.LIST}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
projectId={projectId ?? undefined}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getIndex("state_id")}
|
||||
isForWorkItemCreation={!id}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const StateMarksAsDefault: FC<TStateMarksAsDefault> = observer((props) =>
|
|||
return (
|
||||
<button
|
||||
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"
|
||||
)}
|
||||
disabled={isDefault || isLoading}
|
||||
|
|
|
|||
|
|
@ -16,16 +16,11 @@ type TProjectState = {
|
|||
export const ProjectStateRoot: FC<TProjectState> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// hooks
|
||||
const { groupedProjectStates, fetchProjectStates, fetchProjectStateTransitions } = useProjectState();
|
||||
const { groupedProjectStates, fetchProjectStates } = useProjectState();
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
|
||||
workspaceSlug && projectId
|
||||
? () => {
|
||||
fetchProjectStates(workspaceSlug.toString(), projectId.toString());
|
||||
fetchProjectStateTransitions(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
: null,
|
||||
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null,
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import { SetStateAction } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { GripVertical, Pencil } from "lucide-react";
|
||||
// Plane
|
||||
// plane imports
|
||||
import { IState } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// Plane-web
|
||||
import { StateTransitionCount } from "@/plane-web/components/workflow";
|
||||
import { IStateWorkFlow } from "@/plane-web/types";
|
||||
//
|
||||
// local imports
|
||||
import { StateDelete, StateMarksAsDefault } from "./options";
|
||||
|
||||
export type StateItemTitleProps = {
|
||||
|
|
@ -17,31 +14,37 @@ export type StateItemTitleProps = {
|
|||
stateCount: number;
|
||||
disabled: boolean;
|
||||
state: IState;
|
||||
currentTransitionMap?: IStateWorkFlow;
|
||||
shouldShowDescription?: boolean;
|
||||
};
|
||||
|
||||
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 (
|
||||
<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 */}
|
||||
{!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" />
|
||||
</div>
|
||||
)}
|
||||
{/* state icon */}
|
||||
<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>
|
||||
{/* state title and description */}
|
||||
<div className="text-sm px-2 min-h-5">
|
||||
<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>
|
||||
{/* Transition count */}
|
||||
<StateTransitionCount currentTransitionMap={currentTransitionMap} />
|
||||
</div>
|
||||
|
||||
{!disabled && (
|
||||
|
|
|
|||
|
|
@ -10,14 +10,12 @@ import { TDraggableData } from "@plane/constants";
|
|||
import { IState, TStateGroups } from "@plane/types";
|
||||
import { DropIndicator } from "@plane/ui";
|
||||
// components
|
||||
import { StateUpdate } from "@/components/project-states";
|
||||
import { StateItemTitle, StateUpdate } from "@/components/project-states";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getCurrentStateSequence } from "@/helpers/state.helper";
|
||||
// hooks
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
// Plane-web
|
||||
import { StateItemChild } from "@/plane-web/components/workflow";
|
||||
|
||||
type TStateItem = {
|
||||
workspaceSlug: string;
|
||||
|
|
@ -133,7 +131,7 @@ export const StateItem: FC<TStateItem> = observer((props) => {
|
|||
totalStates === 1 ? `cursor-auto` : `cursor-grab`
|
||||
)}
|
||||
>
|
||||
<StateItemChild
|
||||
<StateItemTitle
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
setUpdateStateModal={setUpdateStateModal}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
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) => {
|
||||
const { fetchWorkspaceLabels } = useLabel();
|
||||
|
|
@ -37,4 +40,7 @@ export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | u
|
|||
workspaceSlug ? () => getWorkspaceEstimates(workspaceSlug.toString()) : null,
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
|
||||
// fetch extended issue properties
|
||||
useWorkspaceIssuePropertiesExtended(workspaceSlug);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
|||
const {
|
||||
project: { fetchProjectMembers },
|
||||
} = useMember();
|
||||
const { fetchProjectStates, fetchProjectStateTransitions } = useProjectState();
|
||||
const { fetchProjectStates } = useProjectState();
|
||||
const { fetchProjectLabels } = useLabel();
|
||||
const { getProjectEstimates } = useProjectEstimates();
|
||||
|
||||
|
|
@ -118,12 +118,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
|||
// fetching project states
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
|
||||
workspaceSlug && projectId
|
||||
? () => {
|
||||
fetchProjectStates(workspaceSlug.toString(), projectId.toString());
|
||||
fetchProjectStateTransitions(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
: null,
|
||||
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null,
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
// fetching project estimates
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { computedFn } from "mobx-utils";
|
|||
import { STATE_GROUPS } from "@plane/constants";
|
||||
import { IState } from "@plane/types";
|
||||
// helpers
|
||||
import { convertStringArrayToBooleanObject } from "@/helpers/array.helper";
|
||||
import { sortStates } from "@/helpers/state.helper";
|
||||
// plane web
|
||||
import { syncIssuesWithDeletedStates } from "@/local-db/utils/load-workspace";
|
||||
|
|
@ -25,10 +24,6 @@ export interface IStateStore {
|
|||
// computed actions
|
||||
getStateById: (stateId: 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
|
||||
fetchProjectStates: (workspaceSlug: string, projectId: string) => Promise<IState[]>;
|
||||
fetchWorkspaceStates: (workspaceSlug: string) => Promise<IState[]>;
|
||||
|
|
@ -48,8 +43,6 @@ export interface IStateStore {
|
|||
stateId: string,
|
||||
payload: Partial<IState>
|
||||
) => Promise<void>;
|
||||
//Dummy method
|
||||
fetchProjectStateTransitions: (workspaceSlug: string, projectId: string) => void;
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param workspaceSlug
|
||||
|
|
@ -292,14 +271,11 @@ export class StateStore implements IStateStore {
|
|||
});
|
||||
// updating using api
|
||||
await this.stateService.patchState(workspaceSlug, projectId, stateId, payload);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// reverting back to old state group if api fails
|
||||
runInAction(() => {
|
||||
this.stateMap = originalStates;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Dummy method
|
||||
fetchProjectStateTransitions = (workspaceSlug: string, projectId: string) => {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from "./projects";
|
||||
export * from "./issue-types";
|
||||
export * from "ce/types/state.d";
|
||||
|
|
|
|||
|
|
@ -37,3 +37,13 @@
|
|||
[cmdk-item][aria-selected="true"] {
|
||||
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