Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const ProjectViewIssuesPage = observer(() => {
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name && projectView?.name ? `${project?.name} - ${projectView?.name}` : undefined;

const { error } = useSWR(
const { error, isLoading } = useSWR(
workspaceSlug && projectId && viewId ? `VIEW_DETAILS_${viewId.toString()}` : null,
workspaceSlug && projectId && viewId
? () => fetchViewDetails(workspaceSlug.toString(), projectId.toString(), viewId.toString())
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/automation/auto-archive-automation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
const isAdmin = allowPermissions(
[EUserPermissions.ADMIN],
EUserPermissionsLevel.PROJECT,
currentProjectDetails?.workspace_detail.slug,
currentProjectDetails?.workspace_detail?.slug,
currentProjectDetails?.id
);

Expand Down
2 changes: 1 addition & 1 deletion web/core/components/inbox/content/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
EUserPermissionsLevel.PROJECT
);
const isGuest = projectPermissionsByWorkspaceSlugAndProjectId(workspaceSlug, projectId) === EUserPermissions.GUEST;
const isOwner = inboxIssue.issue.created_by === currentUser?.id;
const isOwner = inboxIssue?.issue.created_by === currentUser?.id;
const readOnly = !isOwner && isGuest;

if (!inboxIssue) return <></>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
{/* labels */}
<div className="h-7">
<IssueLabelSelect
createLabelEnabled={false}
setIsOpen={() => {}}
value={data?.label_ids || []}
onChange={(labelIds) => handleData("label_ids", labelIds)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ import { ETabIndices } from "@/constants/tab-indices";
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
// hooks
import { useProjectEstimates, useProject } from "@/hooks/store";
import { useProjectEstimates, useProject, useUserPermissions } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import { IssueIdentifier } from "@/plane-web/components/issues";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

type TIssueDefaultPropertiesProps = {
control: Control<TIssue>;
Expand Down Expand Up @@ -67,11 +68,14 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
const { areEstimateEnabledByProjectId } = useProjectEstimates();
const { getProjectById } = useProject();
const { isMobile } = usePlatformOS();
const { allowPermissions } = useUserPermissions();
// derived values
const projectDetails = getProjectById(projectId);

const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);

const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);

const minDate = getDate(startDate);
minDate?.setDate(minDate.getDate());

Expand Down Expand Up @@ -150,6 +154,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
}}
projectId={projectId ?? undefined}
tabIndex={getIndex("label_ids")}
createLabelEnabled={canCreateLabel}
/>
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/issues/select/label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
label,
disabled = false,
tabIndex,
createLabelEnabled = true,
createLabelEnabled = false,
buttonClassName,
} = props;
// router
Expand Down
86 changes: 51 additions & 35 deletions web/core/components/labels/label-block/label-item-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,19 @@ interface ILabelItemBlock {
handleLabelDelete: (label: IIssueLabel) => void;
isLabelGroup?: boolean;
dragHandleRef: MutableRefObject<HTMLButtonElement | null>;
disabled?: boolean;
}

export const LabelItemBlock = (props: ILabelItemBlock) => {
const { label, isDragging, customMenuItems, handleLabelDelete, isLabelGroup, dragHandleRef } = props;
const {
label,
isDragging,
customMenuItems,
handleLabelDelete,
isLabelGroup,
dragHandleRef,
disabled = false,
} = props;
// states
const [isMenuActive, setIsMenuActive] = useState(false);
// refs
Expand All @@ -42,44 +51,51 @@ export const LabelItemBlock = (props: ILabelItemBlock) => {
return (
<div className="group flex items-center">
<div className="flex items-center">
<DragHandle
className={cn("opacity-0 group-hover:opacity-100", {
"opacity-100": isDragging,
})}
ref={dragHandleRef}
/>
{!disabled && (
<DragHandle
className={cn("opacity-0 group-hover:opacity-100", {
"opacity-100": isDragging,
})}
ref={dragHandleRef}
/>
)}
<LabelName color={label.color} name={label.name} isGroup={isLabelGroup ?? false} />
</div>

<div
ref={actionSectionRef}
className={`absolute right-2.5 flex items-start gap-3.5 px-4 ${
isMenuActive || isLabelGroup
? "opacity-100"
: "opacity-0 group-hover:pointer-events-auto group-hover:opacity-100"
} ${isLabelGroup && "-top-0.5"}`}
>
<CustomMenu ellipsis menuButtonOnClick={() => setIsMenuActive(!isMenuActive)} useCaptureForOutsideClick>
{customMenuItems.map(
({ isVisible, onClick, CustomIcon, text, key }) =>
isVisible && (
<CustomMenu.MenuItem key={key} onClick={() => onClick(label)}>
<span className="flex items-center justify-start gap-2">
<CustomIcon className="h-4 w-4" />
<span>{text}</span>
</span>
</CustomMenu.MenuItem>
)
{!disabled && (
<div
ref={actionSectionRef}
className={`absolute right-2.5 flex items-start gap-3.5 px-4 ${
isMenuActive || isLabelGroup
? "opacity-100"
: "opacity-0 group-hover:pointer-events-auto group-hover:opacity-100"
} ${isLabelGroup && "-top-0.5"}`}
>
<CustomMenu ellipsis menuButtonOnClick={() => setIsMenuActive(!isMenuActive)} useCaptureForOutsideClick>
{customMenuItems.map(
({ isVisible, onClick, CustomIcon, text, key }) =>
isVisible && (
<CustomMenu.MenuItem key={key} onClick={() => onClick(label)}>
<span className="flex items-center justify-start gap-2">
<CustomIcon className="h-4 w-4" />
<span>{text}</span>
</span>
</CustomMenu.MenuItem>
)
)}
</CustomMenu>
{!isLabelGroup && (
<div className="py-0.5">
<button
className="flex h-4 w-4 items-center justify-start gap-2"
onClick={() => handleLabelDelete(label)}
>
<X className="h-4 w-4 flex-shrink-0 text-custom-sidebar-text-400" />
</button>
</div>
)}
</CustomMenu>
{!isLabelGroup && (
<div className="py-0.5">
<button className="flex h-4 w-4 items-center justify-start gap-2" onClick={() => handleLabelDelete(label)}>
<X className="h-4 w-4 flex-shrink-0 text-custom-sidebar-text-400" />
</button>
</div>
)}
</div>
</div>
)}
</div>
);
};
13 changes: 12 additions & 1 deletion web/core/components/labels/project-setting-label-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@ type Props = {
droppedLabelId: string | undefined,
dropAtEndOfList: boolean
) => void;
isEditable?: boolean;
};

export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
const { label, setIsUpdating, handleLabelDelete, isChild, isLastChild, isParentDragging = false, onDrop } = props;
const {
label,
setIsUpdating,
handleLabelDelete,
isChild,
isLastChild,
isParentDragging = false,
onDrop,
isEditable = false,
} = props;
// states
const [isEditLabelForm, setEditLabelForm] = useState(false);
// router
Expand Down Expand Up @@ -91,6 +101,7 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
customMenuItems={customMenuItems}
handleLabelDelete={handleLabelDelete}
dragHandleRef={dragHandleRef}
disabled={!isEditable}
/>
)}
</div>
Expand Down
15 changes: 11 additions & 4 deletions web/core/components/labels/project-setting-label-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
ProjectSettingLabelItem,
} from "@/components/labels";
import { EmptyStateType } from "@/constants/empty-state";
import { useLabel } from "@/hooks/store";
import { useLabel, useUserPermissions } from "@/hooks/store";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// components
// ui
// types
Expand All @@ -31,6 +32,10 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
const { workspaceSlug, projectId } = useParams();
// store hooks
const { projectLabels, updateLabelPosition, projectLabelsTree } = useLabel();
const { allowPermissions } = useUserPermissions();

// derived values
const isEditable = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);

const newLabel = () => {
setIsUpdating(false);
Expand Down Expand Up @@ -65,9 +70,11 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
/>
<div className="flex items-center justify-between border-b border-custom-border-100 pb-3.5">
<h3 className="text-xl font-medium">Labels</h3>
<Button variant="primary" onClick={newLabel} size="sm">
Add label
</Button>
{isEditable && (
<Button variant="primary" onClick={newLabel} size="sm">
Add label
</Button>
)}
</div>
<div className="w-full py-2">
{showLabelForm && (
Expand Down
23 changes: 16 additions & 7 deletions web/core/components/project-states/group-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Plus } from "lucide-react";
import { IState, TStateGroups } from "@plane/types";
// components
import { StateList, StateCreate } from "@/components/project-states";
import { useUserPermissions } from "@/hooks/store";
import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions";

type TGroupItem = {
workspaceSlug: string;
Expand All @@ -17,22 +19,28 @@ type TGroupItem = {

export const GroupItem: FC<TGroupItem> = observer((props) => {
const { workspaceSlug, projectId, groupKey, groupedStates, states } = props;
// store hooks
const { allowPermissions } = useUserPermissions();
// state
const [createState, setCreateState] = useState(false);

const isEditable = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);

return (
<div className="space-y-3">
<div className="flex justify-between items-center">
<div className="text-base font-medium text-custom-text-200 capitalize">{groupKey}</div>
<div
className="flex-shrink-0 w-6 h-6 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-primary-100/80 hover:text-custom-primary-100"
onClick={() => !createState && setCreateState(true)}
>
<Plus className="w-4 h-4" />
</div>
{isEditable && (
<div
className="flex-shrink-0 w-6 h-6 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-primary-100/80 hover:text-custom-primary-100"
onClick={() => !createState && setCreateState(true)}
>
<Plus className="w-4 h-4" />
</div>
)}
</div>

{createState && (
{isEditable && createState && (
<StateCreate
workspaceSlug={workspaceSlug}
projectId={projectId}
Expand All @@ -48,6 +56,7 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
groupKey={groupKey}
groupedStates={groupedStates}
states={states}
disabled={!isEditable}
/>
</div>
</div>
Expand Down
54 changes: 31 additions & 23 deletions web/core/components/project-states/state-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ type TStateItem = {
groupedStates: Record<string, IState[]>;
totalStates: number;
state: IState;
disabled?: boolean;
};

export const StateItem: FC<TStateItem> = observer((props) => {
const { workspaceSlug, projectId, groupKey, groupedStates, totalStates, state } = props;
const { workspaceSlug, projectId, groupKey, groupedStates, totalStates, state, disabled = false } = props;
// hooks
const { moveStatePosition } = useProjectState();
// states
Expand Down Expand Up @@ -131,7 +132,7 @@ export const StateItem: FC<TStateItem> = observer((props) => {
)}
>
{/* draggable indicator */}
{totalStates != 1 && (
{!disabled && totalStates != 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">
<GripVertical className="w-3 h-3" />
</div>
Expand All @@ -148,28 +149,35 @@ export const StateItem: FC<TStateItem> = observer((props) => {
<p className="text-xs text-custom-text-200">{state.description}</p>
</div>

<div className="hidden group-hover:flex items-center gap-2">
{/* state mark as default option */}
<div className="flex-shrink-0 text-xs transition-all">
<StateMarksAsDefault
workspaceSlug={workspaceSlug}
projectId={projectId}
stateId={state.id}
isDefault={state.default ? true : false}
/>
{!disabled && (
<div className="hidden group-hover:flex items-center gap-2">
{/* state mark as default option */}
<div className="flex-shrink-0 text-xs transition-all">
<StateMarksAsDefault
workspaceSlug={workspaceSlug}
projectId={projectId}
stateId={state.id}
isDefault={state.default ? true : false}
/>
</div>

{/* state edit options */}
<div className="flex items-center gap-1 transition-all">
<button
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
onClick={() => setUpdateStateModal(true)}
>
<Pencil className="w-3 h-3" />
</button>
<StateDelete
workspaceSlug={workspaceSlug}
projectId={projectId}
totalStates={totalStates}
state={state}
/>
</div>
</div>

{/* state edit options */}
<div className="flex items-center gap-1 transition-all">
<button
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
onClick={() => setUpdateStateModal(true)}
>
<Pencil className="w-3 h-3" />
</button>
<StateDelete workspaceSlug={workspaceSlug} projectId={projectId} totalStates={totalStates} state={state} />
</div>
</div>
)}
Comment on lines +152 to +180
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but consider simplifying the conditional expression.

The conditional rendering of the section containing the state mark as default option and edit options based on the disabled property is correct and aligns with the expected behavior of disabling these options when the state item is disabled.

However, as suggested by the static analysis hint, the conditional expression at line 160 can be simplified by directly assigning the result of state.default without using a ternary operator.

Apply this diff to simplify the conditional expression:

- isDefault={state.default ? true : false}
+ isDefault={state.default}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{!disabled && (
<div className="hidden group-hover:flex items-center gap-2">
{/* state mark as default option */}
<div className="flex-shrink-0 text-xs transition-all">
<StateMarksAsDefault
workspaceSlug={workspaceSlug}
projectId={projectId}
stateId={state.id}
isDefault={state.default ? true : false}
/>
</div>
{/* state edit options */}
<div className="flex items-center gap-1 transition-all">
<button
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
onClick={() => setUpdateStateModal(true)}
>
<Pencil className="w-3 h-3" />
</button>
<StateDelete
workspaceSlug={workspaceSlug}
projectId={projectId}
totalStates={totalStates}
state={state}
/>
</div>
</div>
{/* state edit options */}
<div className="flex items-center gap-1 transition-all">
<button
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
onClick={() => setUpdateStateModal(true)}
>
<Pencil className="w-3 h-3" />
</button>
<StateDelete workspaceSlug={workspaceSlug} projectId={projectId} totalStates={totalStates} state={state} />
</div>
</div>
)}
{!disabled && (
<div className="hidden group-hover:flex items-center gap-2">
{/* state mark as default option */}
<div className="flex-shrink-0 text-xs transition-all">
<StateMarksAsDefault
workspaceSlug={workspaceSlug}
projectId={projectId}
stateId={state.id}
isDefault={state.default}
/>
</div>
{/* state edit options */}
<div className="flex items-center gap-1 transition-all">
<button
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
onClick={() => setUpdateStateModal(true)}
>
<Pencil className="w-3 h-3" />
</button>
<StateDelete
workspaceSlug={workspaceSlug}
projectId={projectId}
totalStates={totalStates}
state={state}
/>
</div>
</div>
)}
Tools
Biome

[error] 160-160: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

</div>

{/* draggable drop bottom indicator */}
Expand Down
Loading