mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style: revamp of the issue details sidebar (#2014)
This commit is contained in:
parent
d8bbdc14ac
commit
168e79d6df
@ -48,10 +48,10 @@ export const SidebarAssigneeSelect: React.FC<Props> = ({ value, onChange, disabl
|
|||||||
{value && value.length > 0 && Array.isArray(value) ? (
|
{value && value.length > 0 && Array.isArray(value) ? (
|
||||||
<div className="-my-0.5 flex items-center gap-2">
|
<div className="-my-0.5 flex items-center gap-2">
|
||||||
<AssigneesList userIds={value} length={3} showLength={false} />
|
<AssigneesList userIds={value} length={3} showLength={false} />
|
||||||
<span className="text-custom-text-100 text-sm">{value.length} Assignees</span>
|
<span className="text-custom-text-100 text-xs">{value.length} Assignees</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<button type="button" className="bg-custom-background-80 px-2.5 py-0.5 text-sm rounded">
|
<button type="button" className="bg-custom-background-80 px-2.5 py-0.5 text-xs rounded">
|
||||||
No assignees
|
No assignees
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
@ -18,7 +18,6 @@ type Props = {
|
|||||||
issueId?: string;
|
issueId?: string;
|
||||||
submitChanges: (formData: Partial<IIssue>) => void;
|
submitChanges: (formData: Partial<IIssue>) => void;
|
||||||
watch: UseFormWatch<IIssue>;
|
watch: UseFormWatch<IIssue>;
|
||||||
userAuth: UserAuth;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,7 +25,6 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
issueId,
|
issueId,
|
||||||
submitChanges,
|
submitChanges,
|
||||||
watch,
|
watch,
|
||||||
userAuth,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
||||||
@ -73,8 +71,6 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ExistingIssuesListModal
|
<ExistingIssuesListModal
|
||||||
@ -128,11 +124,11 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex w-full text-custom-text-200 ${
|
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 ${
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
||||||
} items-center justify-between gap-1 rounded-md border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-300 focus:outline-none`}
|
}`}
|
||||||
onClick={() => setIsBlockedModalOpen(true)}
|
onClick={() => setIsBlockedModalOpen(true)}
|
||||||
disabled={isNotAllowed}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Select issues
|
Select issues
|
||||||
</button>
|
</button>
|
||||||
|
@ -18,7 +18,6 @@ type Props = {
|
|||||||
issueId?: string;
|
issueId?: string;
|
||||||
submitChanges: (formData: Partial<IIssue>) => void;
|
submitChanges: (formData: Partial<IIssue>) => void;
|
||||||
watch: UseFormWatch<IIssue>;
|
watch: UseFormWatch<IIssue>;
|
||||||
userAuth: UserAuth;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,7 +25,6 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
issueId,
|
issueId,
|
||||||
submitChanges,
|
submitChanges,
|
||||||
watch,
|
watch,
|
||||||
userAuth,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
|
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
|
||||||
@ -73,8 +71,6 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ExistingIssuesListModal
|
<ExistingIssuesListModal
|
||||||
@ -130,11 +126,11 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex w-full text-custom-text-200 ${
|
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 ${
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
||||||
} items-center justify-between gap-1 rounded-md border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-300 focus:outline-none`}
|
}`}
|
||||||
onClick={() => setIsBlockerModalOpen(true)}
|
onClick={() => setIsBlockerModalOpen(true)}
|
||||||
disabled={isNotAllowed}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Select issues
|
Select issues
|
||||||
</button>
|
</button>
|
||||||
|
@ -11,24 +11,20 @@ import cyclesService from "services/cycles.service";
|
|||||||
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
||||||
// helper
|
// helper
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// icons
|
|
||||||
import { ContrastIcon } from "components/icons";
|
|
||||||
// types
|
// types
|
||||||
import { ICycle, IIssue, UserAuth } from "types";
|
import { ICycle, IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueDetail: IIssue | undefined;
|
issueDetail: IIssue | undefined;
|
||||||
handleCycleChange: (cycle: ICycle) => void;
|
handleCycleChange: (cycle: ICycle) => void;
|
||||||
userAuth: UserAuth;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarCycleSelect: React.FC<Props> = ({
|
export const SidebarCycleSelect: React.FC<Props> = ({
|
||||||
issueDetail,
|
issueDetail,
|
||||||
handleCycleChange,
|
handleCycleChange,
|
||||||
userAuth,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -63,59 +59,56 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
|||||||
|
|
||||||
const issueCycle = issueDetail?.issue_cycle;
|
const issueCycle = issueDetail?.issue_cycle;
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center py-2">
|
<CustomSelect
|
||||||
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
customButton={
|
||||||
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
|
<Tooltip
|
||||||
<p>Cycle</p>
|
position="left"
|
||||||
</div>
|
tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}
|
||||||
<div className="space-y-1 sm:basis-1/2">
|
|
||||||
<CustomSelect
|
|
||||||
label={
|
|
||||||
<Tooltip
|
|
||||||
position="left"
|
|
||||||
tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}
|
|
||||||
>
|
|
||||||
<span className="w-full max-w-[125px] truncate text-left sm:block">
|
|
||||||
<span className={`${issueCycle ? "text-custom-text-100" : "text-custom-text-200"}`}>
|
|
||||||
{issueCycle ? truncateText(issueCycle.cycle_detail.name, 15) : "No cycle"}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
value={issueCycle ? issueCycle.cycle_detail.id : null}
|
|
||||||
onChange={(value: any) => {
|
|
||||||
!value
|
|
||||||
? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "")
|
|
||||||
: handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle);
|
|
||||||
}}
|
|
||||||
width="w-full"
|
|
||||||
position="right"
|
|
||||||
maxHeight="rg"
|
|
||||||
disabled={isNotAllowed}
|
|
||||||
>
|
>
|
||||||
{incompleteCycles ? (
|
<button
|
||||||
incompleteCycles.length > 0 ? (
|
type="button"
|
||||||
<>
|
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
|
||||||
{incompleteCycles.map((option) => (
|
disabled ? "cursor-not-allowed" : ""
|
||||||
<CustomSelect.Option key={option.id} value={option.id}>
|
}`}
|
||||||
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
>
|
||||||
<span className="w-full truncate">{truncateText(option.name, 25)}</span>
|
<span
|
||||||
</Tooltip>
|
className={`truncate ${issueCycle ? "text-custom-text-100" : "text-custom-text-200"}`}
|
||||||
</CustomSelect.Option>
|
>
|
||||||
))}
|
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
|
||||||
<CustomSelect.Option value={null}>None</CustomSelect.Option>
|
</span>
|
||||||
</>
|
</button>
|
||||||
) : (
|
</Tooltip>
|
||||||
<div className="text-center">No cycles found</div>
|
}
|
||||||
)
|
value={issueCycle ? issueCycle.cycle_detail.id : null}
|
||||||
) : (
|
onChange={(value: any) => {
|
||||||
<Spinner />
|
!value
|
||||||
)}
|
? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "")
|
||||||
</CustomSelect>
|
: handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle);
|
||||||
</div>
|
}}
|
||||||
</div>
|
width="w-full"
|
||||||
|
position="right"
|
||||||
|
maxHeight="rg"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{incompleteCycles ? (
|
||||||
|
incompleteCycles.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{incompleteCycles.map((option) => (
|
||||||
|
<CustomSelect.Option key={option.id} value={option.id}>
|
||||||
|
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
||||||
|
<span className="w-full truncate">{truncateText(option.name, 25)}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</CustomSelect.Option>
|
||||||
|
))}
|
||||||
|
<CustomSelect.Option value={null}>None</CustomSelect.Option>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center">No cycles found</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</CustomSelect>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,9 +14,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabled = false }) => {
|
export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabled = false }) => {
|
||||||
const { isEstimateActive, estimatePoints } = useEstimateOption();
|
const { estimatePoints } = useEstimateOption();
|
||||||
|
|
||||||
if (!isEstimateActive) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
|
@ -10,24 +10,20 @@ import modulesService from "services/modules.service";
|
|||||||
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
||||||
// helper
|
// helper
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// icons
|
|
||||||
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue, IModule, UserAuth } from "types";
|
import { IIssue, IModule } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
|
import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueDetail: IIssue | undefined;
|
issueDetail: IIssue | undefined;
|
||||||
handleModuleChange: (module: IModule) => void;
|
handleModuleChange: (module: IModule) => void;
|
||||||
userAuth: UserAuth;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarModuleSelect: React.FC<Props> = ({
|
export const SidebarModuleSelect: React.FC<Props> = ({
|
||||||
issueDetail,
|
issueDetail,
|
||||||
handleModuleChange,
|
handleModuleChange,
|
||||||
userAuth,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -57,66 +53,60 @@ export const SidebarModuleSelect: React.FC<Props> = ({
|
|||||||
|
|
||||||
const issueModule = issueDetail?.issue_module;
|
const issueModule = issueDetail?.issue_module;
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center py-2">
|
<CustomSelect
|
||||||
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
customButton={
|
||||||
<RectangleGroupIcon className="h-4 w-4 flex-shrink-0" />
|
<Tooltip
|
||||||
<p>Module</p>
|
position="left"
|
||||||
</div>
|
tooltipContent={`${
|
||||||
<div className="space-y-1 sm:basis-1/2">
|
modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"
|
||||||
<CustomSelect
|
}`}
|
||||||
label={
|
>
|
||||||
<Tooltip
|
<button
|
||||||
position="left"
|
type="button"
|
||||||
tooltipContent={`${
|
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
|
||||||
modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"
|
disabled ? "cursor-not-allowed" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`truncate ${
|
||||||
|
issueModule ? "text-custom-text-100" : "text-custom-text-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="w-full max-w-[125px] truncate text-left sm:block">
|
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}
|
||||||
<span
|
</span>
|
||||||
className={`${issueModule ? "text-custom-text-100" : "text-custom-text-200"}`}
|
</button>
|
||||||
>
|
</Tooltip>
|
||||||
{truncateText(
|
}
|
||||||
`${modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}`,
|
value={issueModule ? issueModule.module_detail?.id : null}
|
||||||
15
|
onChange={(value: any) => {
|
||||||
)}
|
!value
|
||||||
</span>
|
? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "")
|
||||||
</span>
|
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
|
||||||
</Tooltip>
|
}}
|
||||||
}
|
width="w-full"
|
||||||
value={issueModule ? issueModule.module_detail?.id : null}
|
position="right"
|
||||||
onChange={(value: any) => {
|
maxHeight="rg"
|
||||||
!value
|
disabled={disabled}
|
||||||
? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "")
|
>
|
||||||
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
|
{modules ? (
|
||||||
}}
|
modules.length > 0 ? (
|
||||||
width="w-full"
|
<>
|
||||||
position="right"
|
{modules.map((option) => (
|
||||||
maxHeight="rg"
|
<CustomSelect.Option key={option.id} value={option.id}>
|
||||||
disabled={isNotAllowed}
|
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
||||||
>
|
<span className="w-full truncate">{truncateText(option.name, 25)}</span>
|
||||||
{modules ? (
|
</Tooltip>
|
||||||
modules.length > 0 ? (
|
</CustomSelect.Option>
|
||||||
<>
|
))}
|
||||||
{modules.map((option) => (
|
<CustomSelect.Option value={null}>None</CustomSelect.Option>
|
||||||
<CustomSelect.Option key={option.id} value={option.id}>
|
</>
|
||||||
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
) : (
|
||||||
<span className="w-full truncate">{truncateText(option.name, 25)}</span>
|
<div className="text-center">No modules found</div>
|
||||||
</Tooltip>
|
)
|
||||||
</CustomSelect.Option>
|
) : (
|
||||||
))}
|
<Spinner />
|
||||||
<CustomSelect.Option value={null}>None</CustomSelect.Option>
|
)}
|
||||||
</>
|
</CustomSelect>
|
||||||
) : (
|
|
||||||
<div className="text-center">No modules found</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Spinner />
|
|
||||||
)}
|
|
||||||
</CustomSelect>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,8 +2,6 @@ import React, { useState } from "react";
|
|||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// icons
|
|
||||||
import { UserIcon } from "@heroicons/react/24/outline";
|
|
||||||
// components
|
// components
|
||||||
import { ParentIssuesListModal } from "components/issues";
|
import { ParentIssuesListModal } from "components/issues";
|
||||||
// types
|
// types
|
||||||
@ -12,14 +10,12 @@ import { IIssue, ISearchIssueResponse, UserAuth } from "types";
|
|||||||
type Props = {
|
type Props = {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
issueDetails: IIssue | undefined;
|
issueDetails: IIssue | undefined;
|
||||||
userAuth: UserAuth;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarParentSelect: React.FC<Props> = ({
|
export const SidebarParentSelect: React.FC<Props> = ({
|
||||||
onChange,
|
onChange,
|
||||||
issueDetails,
|
issueDetails,
|
||||||
userAuth,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [isParentModalOpen, setIsParentModalOpen] = useState(false);
|
const [isParentModalOpen, setIsParentModalOpen] = useState(false);
|
||||||
@ -28,42 +24,34 @@ export const SidebarParentSelect: React.FC<Props> = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, issueId } = router.query;
|
const { projectId, issueId } = router.query;
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center py-2">
|
<>
|
||||||
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
<ParentIssuesListModal
|
||||||
<UserIcon className="h-4 w-4 flex-shrink-0" />
|
isOpen={isParentModalOpen}
|
||||||
<p>Parent</p>
|
handleClose={() => setIsParentModalOpen(false)}
|
||||||
</div>
|
onChange={(issue) => {
|
||||||
<div className="sm:basis-1/2">
|
onChange(issue.id);
|
||||||
<ParentIssuesListModal
|
setSelectedParentIssue(issue);
|
||||||
isOpen={isParentModalOpen}
|
}}
|
||||||
handleClose={() => setIsParentModalOpen(false)}
|
issueId={issueId as string}
|
||||||
onChange={(issue) => {
|
projectId={projectId as string}
|
||||||
onChange(issue.id);
|
/>
|
||||||
setSelectedParentIssue(issue);
|
<button
|
||||||
}}
|
type="button"
|
||||||
issueId={issueId as string}
|
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 ${
|
||||||
projectId={projectId as string}
|
disabled ? "cursor-not-allowed" : "cursor-pointer "
|
||||||
/>
|
}`}
|
||||||
<button
|
onClick={() => setIsParentModalOpen(true)}
|
||||||
type="button"
|
disabled={disabled}
|
||||||
className={`flex w-full ${
|
>
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
{selectedParentIssue && issueDetails?.parent ? (
|
||||||
} items-center justify-between gap-1 rounded-md border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-300 focus:outline-none`}
|
`${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
|
||||||
onClick={() => setIsParentModalOpen(true)}
|
) : !selectedParentIssue && issueDetails?.parent ? (
|
||||||
disabled={isNotAllowed}
|
`${issueDetails.parent_detail?.project_detail.identifier}-${issueDetails.parent_detail?.sequence_id}`
|
||||||
>
|
) : (
|
||||||
{selectedParentIssue && issueDetails?.parent ? (
|
<span className="text-custom-text-200">Select issue</span>
|
||||||
`${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
|
)}
|
||||||
) : !selectedParentIssue && issueDetails?.parent ? (
|
</button>
|
||||||
`${issueDetails.parent_detail?.project_detail.identifier}-${issueDetails.parent_detail?.sequence_id}`
|
</>
|
||||||
) : (
|
|
||||||
<span className="text-custom-text-200">Select issue</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabl
|
|||||||
customButton={
|
customButton={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex items-center gap-1.5 text-left text-sm capitalize rounded px-2.5 py-0.5 ${
|
className={`flex items-center gap-1.5 text-left text-xs capitalize rounded px-2.5 py-0.5 ${
|
||||||
value === "urgent"
|
value === "urgent"
|
||||||
? "border-red-500/20 bg-red-500/20 text-red-500"
|
? "border-red-500/20 bg-red-500/20 text-red-500"
|
||||||
: value === "high"
|
: value === "high"
|
||||||
|
@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
|
|||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
customButton={
|
customButton={
|
||||||
<button type="button" className="bg-custom-background-80 text-sm rounded px-2.5 py-0.5">
|
<button type="button" className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
|
||||||
{selectedState ? (
|
{selectedState ? (
|
||||||
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
||||||
{getStateGroupIcon(
|
{getStateGroupIcon(
|
||||||
|
@ -10,6 +10,7 @@ import { Controller, UseFormWatch } from "react-hook-form";
|
|||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
|
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
|
||||||
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
@ -42,6 +43,8 @@ import {
|
|||||||
ChartBarIcon,
|
ChartBarIcon,
|
||||||
UserGroupIcon,
|
UserGroupIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
|
UserIcon,
|
||||||
|
RectangleGroupIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
@ -49,6 +52,7 @@ import { copyTextToClipboard } from "helpers/string.helper";
|
|||||||
import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types";
|
import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
import { ContrastIcon } from "components/icons";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
control: any;
|
control: any;
|
||||||
@ -93,6 +97,8 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { user } = useUserAuth();
|
const { user } = useUserAuth();
|
||||||
|
|
||||||
|
const { isEstimateActive } = useEstimateOption();
|
||||||
|
|
||||||
const { loading, handleSubscribe, handleUnsubscribe, subscribed } =
|
const { loading, handleSubscribe, handleUnsubscribe, subscribed } =
|
||||||
useUserIssueNotificationSubscription(workspaceSlug, projectId, issueId);
|
useUserIssueNotificationSubscription(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
@ -403,22 +409,51 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) &&
|
||||||
|
isEstimateActive && (
|
||||||
|
<div className="flex flex-wrap items-center py-2">
|
||||||
|
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
||||||
|
<PlayIcon className="h-4 w-4 flex-shrink-0 -rotate-90" />
|
||||||
|
<p>Estimate</p>
|
||||||
|
</div>
|
||||||
|
<div className="sm:basis-1/2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="estimate_point"
|
||||||
|
render={({ field: { value } }) => (
|
||||||
|
<SidebarEstimateSelect
|
||||||
|
value={value}
|
||||||
|
onChange={(val: number | null) =>
|
||||||
|
submitChanges({ estimate_point: val })
|
||||||
|
}
|
||||||
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showSecondSection && (
|
||||||
|
<div className="py-1">
|
||||||
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
|
||||||
<div className="flex flex-wrap items-center py-2">
|
<div className="flex flex-wrap items-center py-2">
|
||||||
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
||||||
<PlayIcon className="h-4 w-4 flex-shrink-0 -rotate-90" />
|
<UserIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Estimate</p>
|
<p>Parent</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:basis-1/2">
|
<div className="sm:basis-1/2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="estimate_point"
|
name="parent"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { onChange } }) => (
|
||||||
<SidebarEstimateSelect
|
<SidebarParentSelect
|
||||||
value={value}
|
onChange={(val: string) => {
|
||||||
onChange={(val: number | null) =>
|
submitChanges({ parent: val });
|
||||||
submitChanges({ estimate_point: val })
|
onChange(val);
|
||||||
}
|
}}
|
||||||
|
issueDetails={issueDetail}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -426,34 +461,12 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{showSecondSection && (
|
|
||||||
<div className="py-1">
|
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="parent"
|
|
||||||
render={({ field: { onChange } }) => (
|
|
||||||
<SidebarParentSelect
|
|
||||||
onChange={(val: string) => {
|
|
||||||
submitChanges({ parent: val });
|
|
||||||
onChange(val);
|
|
||||||
}}
|
|
||||||
issueDetails={issueDetail}
|
|
||||||
userAuth={memberRole}
|
|
||||||
disabled={uneditable}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && (
|
||||||
<SidebarBlockerSelect
|
<SidebarBlockerSelect
|
||||||
issueId={issueId as string}
|
issueId={issueId as string}
|
||||||
submitChanges={submitChanges}
|
submitChanges={submitChanges}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
userAuth={memberRole}
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
disabled={uneditable}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
|
||||||
@ -461,8 +474,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
issueId={issueId as string}
|
issueId={issueId as string}
|
||||||
submitChanges={submitChanges}
|
submitChanges={submitChanges}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
userAuth={memberRole}
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
disabled={uneditable}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
||||||
@ -484,8 +496,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
start_date: val,
|
start_date: val,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="bg-custom-background-100"
|
className="bg-custom-background-80 border-none"
|
||||||
wrapperClassName="w-full"
|
|
||||||
maxDate={maxDate ?? undefined}
|
maxDate={maxDate ?? undefined}
|
||||||
disabled={isNotAllowed || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
@ -513,8 +524,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
target_date: val,
|
target_date: val,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="bg-custom-background-100"
|
className="bg-custom-background-80 border-none"
|
||||||
wrapperClassName="w-full"
|
|
||||||
minDate={minDate ?? undefined}
|
minDate={minDate ?? undefined}
|
||||||
disabled={isNotAllowed || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
@ -528,20 +538,34 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
{showThirdSection && (
|
{showThirdSection && (
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && (
|
||||||
<SidebarCycleSelect
|
<div className="flex flex-wrap items-center py-2">
|
||||||
issueDetail={issueDetail}
|
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:w-1/2">
|
||||||
handleCycleChange={handleCycleChange}
|
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
userAuth={memberRole}
|
<p>Cycle</p>
|
||||||
disabled={uneditable}
|
</div>
|
||||||
/>
|
<div className="space-y-1 sm:w-1/2">
|
||||||
|
<SidebarCycleSelect
|
||||||
|
issueDetail={issueDetail}
|
||||||
|
handleCycleChange={handleCycleChange}
|
||||||
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("module")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("module")) && (
|
||||||
<SidebarModuleSelect
|
<div className="flex flex-wrap items-center py-2">
|
||||||
issueDetail={issueDetail}
|
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:w-1/2">
|
||||||
handleModuleChange={handleModuleChange}
|
<RectangleGroupIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
userAuth={memberRole}
|
<p>Module</p>
|
||||||
disabled={uneditable}
|
</div>
|
||||||
/>
|
<div className="space-y-1 sm:w-1/2">
|
||||||
|
<SidebarModuleSelect
|
||||||
|
issueDetail={issueDetail}
|
||||||
|
handleModuleChange={handleModuleChange}
|
||||||
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
Loading…
Reference in New Issue
Block a user