style: revamp of the issue details sidebar (#2014)

This commit is contained in:
Aaryan Khandelwal 2023-08-29 20:15:12 +05:30 committed by GitHub
parent d8bbdc14ac
commit 168e79d6df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 219 additions and 234 deletions

View File

@ -48,10 +48,10 @@ export const SidebarAssigneeSelect: React.FC<Props> = ({ value, onChange, disabl
{value && value.length > 0 && Array.isArray(value) ? (
<div className="-my-0.5 flex items-center gap-2">
<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>
) : (
<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
</button>
)}

View File

@ -18,7 +18,6 @@ type Props = {
issueId?: string;
submitChanges: (formData: Partial<IIssue>) => void;
watch: UseFormWatch<IIssue>;
userAuth: UserAuth;
disabled?: boolean;
};
@ -26,7 +25,6 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
issueId,
submitChanges,
watch,
userAuth,
disabled = false,
}) => {
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
@ -73,8 +71,6 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
handleClose();
};
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
return (
<>
<ExistingIssuesListModal
@ -128,11 +124,11 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
</div>
<button
type="button"
className={`flex w-full text-custom-text-200 ${
isNotAllowed ? "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`}
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 ${
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
}`}
onClick={() => setIsBlockedModalOpen(true)}
disabled={isNotAllowed}
disabled={disabled}
>
Select issues
</button>

View File

@ -18,7 +18,6 @@ type Props = {
issueId?: string;
submitChanges: (formData: Partial<IIssue>) => void;
watch: UseFormWatch<IIssue>;
userAuth: UserAuth;
disabled?: boolean;
};
@ -26,7 +25,6 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
issueId,
submitChanges,
watch,
userAuth,
disabled = false,
}) => {
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
@ -73,8 +71,6 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
handleClose();
};
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
return (
<>
<ExistingIssuesListModal
@ -130,11 +126,11 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
</div>
<button
type="button"
className={`flex w-full text-custom-text-200 ${
isNotAllowed ? "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`}
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 ${
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
}`}
onClick={() => setIsBlockerModalOpen(true)}
disabled={isNotAllowed}
disabled={disabled}
>
Select issues
</button>

View File

@ -11,24 +11,20 @@ import cyclesService from "services/cycles.service";
import { Spinner, CustomSelect, Tooltip } from "components/ui";
// helper
import { truncateText } from "helpers/string.helper";
// icons
import { ContrastIcon } from "components/icons";
// types
import { ICycle, IIssue, UserAuth } from "types";
import { ICycle, IIssue } from "types";
// fetch-keys
import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
type Props = {
issueDetail: IIssue | undefined;
handleCycleChange: (cycle: ICycle) => void;
userAuth: UserAuth;
disabled?: boolean;
};
export const SidebarCycleSelect: React.FC<Props> = ({
issueDetail,
handleCycleChange,
userAuth,
disabled = false,
}) => {
const router = useRouter();
@ -63,59 +59,56 @@ export const SidebarCycleSelect: React.FC<Props> = ({
const issueCycle = issueDetail?.issue_cycle;
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
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">
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
<p>Cycle</p>
</div>
<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}
<CustomSelect
customButton={
<Tooltip
position="left"
tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}
>
{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>
</div>
</div>
<button
type="button"
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
disabled ? "cursor-not-allowed" : ""
}`}
>
<span
className={`truncate ${issueCycle ? "text-custom-text-100" : "text-custom-text-200"}`}
>
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
</span>
</button>
</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={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>
);
};

View File

@ -14,9 +14,7 @@ type Props = {
};
export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabled = false }) => {
const { isEstimateActive, estimatePoints } = useEstimateOption();
if (!isEstimateActive) return null;
const { estimatePoints } = useEstimateOption();
return (
<CustomSelect

View File

@ -10,24 +10,20 @@ import modulesService from "services/modules.service";
import { Spinner, CustomSelect, Tooltip } from "components/ui";
// helper
import { truncateText } from "helpers/string.helper";
// icons
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
// types
import { IIssue, IModule, UserAuth } from "types";
import { IIssue, IModule } from "types";
// fetch-keys
import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
type Props = {
issueDetail: IIssue | undefined;
handleModuleChange: (module: IModule) => void;
userAuth: UserAuth;
disabled?: boolean;
};
export const SidebarModuleSelect: React.FC<Props> = ({
issueDetail,
handleModuleChange,
userAuth,
disabled = false,
}) => {
const router = useRouter();
@ -57,66 +53,60 @@ export const SidebarModuleSelect: React.FC<Props> = ({
const issueModule = issueDetail?.issue_module;
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
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">
<RectangleGroupIcon className="h-4 w-4 flex-shrink-0" />
<p>Module</p>
</div>
<div className="space-y-1 sm:basis-1/2">
<CustomSelect
label={
<Tooltip
position="left"
tooltipContent={`${
modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"
<CustomSelect
customButton={
<Tooltip
position="left"
tooltipContent={`${
modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"
}`}
>
<button
type="button"
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
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">
<span
className={`${issueModule ? "text-custom-text-100" : "text-custom-text-200"}`}
>
{truncateText(
`${modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}`,
15
)}
</span>
</span>
</Tooltip>
}
value={issueModule ? issueModule.module_detail?.id : null}
onChange={(value: any) => {
!value
? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "")
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
}}
width="w-full"
position="right"
maxHeight="rg"
disabled={isNotAllowed}
>
{modules ? (
modules.length > 0 ? (
<>
{modules.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 modules found</div>
)
) : (
<Spinner />
)}
</CustomSelect>
</div>
</div>
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}
</span>
</button>
</Tooltip>
}
value={issueModule ? issueModule.module_detail?.id : null}
onChange={(value: any) => {
!value
? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "")
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
}}
width="w-full"
position="right"
maxHeight="rg"
disabled={disabled}
>
{modules ? (
modules.length > 0 ? (
<>
{modules.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 modules found</div>
)
) : (
<Spinner />
)}
</CustomSelect>
);
};

View File

@ -2,8 +2,6 @@ import React, { useState } from "react";
import { useRouter } from "next/router";
// icons
import { UserIcon } from "@heroicons/react/24/outline";
// components
import { ParentIssuesListModal } from "components/issues";
// types
@ -12,14 +10,12 @@ import { IIssue, ISearchIssueResponse, UserAuth } from "types";
type Props = {
onChange: (value: string) => void;
issueDetails: IIssue | undefined;
userAuth: UserAuth;
disabled?: boolean;
};
export const SidebarParentSelect: React.FC<Props> = ({
onChange,
issueDetails,
userAuth,
disabled = false,
}) => {
const [isParentModalOpen, setIsParentModalOpen] = useState(false);
@ -28,42 +24,34 @@ export const SidebarParentSelect: React.FC<Props> = ({
const router = useRouter();
const { projectId, issueId } = router.query;
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
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">
<UserIcon className="h-4 w-4 flex-shrink-0" />
<p>Parent</p>
</div>
<div className="sm:basis-1/2">
<ParentIssuesListModal
isOpen={isParentModalOpen}
handleClose={() => setIsParentModalOpen(false)}
onChange={(issue) => {
onChange(issue.id);
setSelectedParentIssue(issue);
}}
issueId={issueId as string}
projectId={projectId as string}
/>
<button
type="button"
className={`flex w-full ${
isNotAllowed ? "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={() => setIsParentModalOpen(true)}
disabled={isNotAllowed}
>
{selectedParentIssue && issueDetails?.parent ? (
`${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
) : !selectedParentIssue && issueDetails?.parent ? (
`${issueDetails.parent_detail?.project_detail.identifier}-${issueDetails.parent_detail?.sequence_id}`
) : (
<span className="text-custom-text-200">Select issue</span>
)}
</button>
</div>
</div>
<>
<ParentIssuesListModal
isOpen={isParentModalOpen}
handleClose={() => setIsParentModalOpen(false)}
onChange={(issue) => {
onChange(issue.id);
setSelectedParentIssue(issue);
}}
issueId={issueId as string}
projectId={projectId as string}
/>
<button
type="button"
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 ${
disabled ? "cursor-not-allowed" : "cursor-pointer "
}`}
onClick={() => setIsParentModalOpen(true)}
disabled={disabled}
>
{selectedParentIssue && issueDetails?.parent ? (
`${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
) : !selectedParentIssue && issueDetails?.parent ? (
`${issueDetails.parent_detail?.project_detail.identifier}-${issueDetails.parent_detail?.sequence_id}`
) : (
<span className="text-custom-text-200">Select issue</span>
)}
</button>
</>
);
};

View File

@ -18,7 +18,7 @@ export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabl
customButton={
<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"
? "border-red-500/20 bg-red-500/20 text-red-500"
: value === "high"

View File

@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
return (
<CustomSelect
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 ? (
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
{getStateGroupIcon(

View File

@ -10,6 +10,7 @@ import { Controller, UseFormWatch } from "react-hook-form";
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
import useEstimateOption from "hooks/use-estimate-option";
// services
import issuesService from "services/issues.service";
import modulesService from "services/modules.service";
@ -42,6 +43,8 @@ import {
ChartBarIcon,
UserGroupIcon,
PlayIcon,
UserIcon,
RectangleGroupIcon,
} from "@heroicons/react/24/outline";
// helpers
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";
// fetch-keys
import { ISSUE_DETAILS } from "constants/fetch-keys";
import { ContrastIcon } from "components/icons";
type Props = {
control: any;
@ -93,6 +97,8 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
const { user } = useUserAuth();
const { isEstimateActive } = useEstimateOption();
const { loading, handleSubscribe, handleUnsubscribe, subscribed } =
useUserIssueNotificationSubscription(workspaceSlug, projectId, issueId);
@ -403,22 +409,51 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
</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 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>
<UserIcon className="h-4 w-4 flex-shrink-0" />
<p>Parent</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 })
}
name="parent"
render={({ field: { onChange } }) => (
<SidebarParentSelect
onChange={(val: string) => {
submitChanges({ parent: val });
onChange(val);
}}
issueDetails={issueDetail}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
/>
)}
@ -426,34 +461,12 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
</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")) && (
<SidebarBlockerSelect
issueId={issueId as string}
submitChanges={submitChanges}
watch={watchIssue}
userAuth={memberRole}
disabled={uneditable}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
@ -461,8 +474,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
issueId={issueId as string}
submitChanges={submitChanges}
watch={watchIssue}
userAuth={memberRole}
disabled={uneditable}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
@ -484,8 +496,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
start_date: val,
})
}
className="bg-custom-background-100"
wrapperClassName="w-full"
className="bg-custom-background-80 border-none"
maxDate={maxDate ?? undefined}
disabled={isNotAllowed || uneditable}
/>
@ -513,8 +524,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
target_date: val,
})
}
className="bg-custom-background-100"
wrapperClassName="w-full"
className="bg-custom-background-80 border-none"
minDate={minDate ?? undefined}
disabled={isNotAllowed || uneditable}
/>
@ -528,20 +538,34 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
{showThirdSection && (
<div className="py-1">
{(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && (
<SidebarCycleSelect
issueDetail={issueDetail}
handleCycleChange={handleCycleChange}
userAuth={memberRole}
disabled={uneditable}
/>
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:w-1/2">
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
<p>Cycle</p>
</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")) && (
<SidebarModuleSelect
issueDetail={issueDetail}
handleModuleChange={handleModuleChange}
userAuth={memberRole}
disabled={uneditable}
/>
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:w-1/2">
<RectangleGroupIcon className="h-4 w-4 flex-shrink-0" />
<p>Module</p>
</div>
<div className="space-y-1 sm:w-1/2">
<SidebarModuleSelect
issueDetail={issueDetail}
handleModuleChange={handleModuleChange}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
/>
</div>
</div>
)}
</div>
)}