forked from github/plane
chore: bug fixes and ui/ux enhancements (#2036)
This commit is contained in:
parent
8a95a41100
commit
74bf9062b4
@ -22,8 +22,6 @@ const shortcuts = [
|
||||
{ keys: "↓", description: "Move down" },
|
||||
{ keys: "←", description: "Move left" },
|
||||
{ keys: "→", description: "Move right" },
|
||||
{ keys: "Enter", description: "Select" },
|
||||
{ keys: "Esc", description: "Close" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -353,6 +353,28 @@ const activityDetails: {
|
||||
.
|
||||
</>
|
||||
);
|
||||
else if (activity.verb === "updated")
|
||||
return (
|
||||
<>
|
||||
updated the{" "}
|
||||
<a
|
||||
href={`${activity.old_value}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
link
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
|
@ -158,7 +158,7 @@ export const IssuesFilterView: React.FC = () => {
|
||||
: "text-custom-sidebar-text-200"
|
||||
}`}
|
||||
>
|
||||
View
|
||||
Display
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
|
||||
|
@ -40,9 +40,15 @@ type Props = {
|
||||
label: string | React.ReactNode;
|
||||
value: string | null;
|
||||
onChange: (data: string) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange }) => {
|
||||
export const ImagePickerPopover: React.FC<Props> = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const router = useRouter();
|
||||
@ -117,6 +123,7 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
|
||||
<Popover.Button
|
||||
className="rounded-md border border-custom-border-300 bg-custom-background-100 px-2 py-1 text-xs text-custom-text-200 hover:text-custom-text-100"
|
||||
onClick={() => setIsOpen((prev) => !prev)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{label}
|
||||
</Popover.Button>
|
||||
|
@ -29,6 +29,7 @@ type Props = {
|
||||
isCollapsed: boolean;
|
||||
setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
disableUserActions: boolean;
|
||||
disableAddIssue: boolean;
|
||||
viewProps: IIssueViewProps;
|
||||
};
|
||||
|
||||
@ -39,6 +40,7 @@ export const BoardHeader: React.FC<Props> = ({
|
||||
isCollapsed,
|
||||
setIsCollapsed,
|
||||
disableUserActions,
|
||||
disableAddIssue,
|
||||
viewProps,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
@ -181,7 +183,7 @@ export const BoardHeader: React.FC<Props> = ({
|
||||
<Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" />
|
||||
)}
|
||||
</button>
|
||||
{!disableUserActions && selectedGroup !== "created_by" && (
|
||||
{!disableAddIssue && !disableUserActions && selectedGroup !== "created_by" && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80"
|
||||
|
@ -53,6 +53,8 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
const router = useRouter();
|
||||
const { cycleId, moduleId } = router.query;
|
||||
|
||||
const isSubscribedIssues = router.pathname.includes("subscribed");
|
||||
|
||||
const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
|
||||
|
||||
// Check if it has at least 4 tickets since it is enough to accommodate the Calendar height
|
||||
@ -70,6 +72,7 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
isCollapsed={isCollapsed}
|
||||
setIsCollapsed={setIsCollapsed}
|
||||
disableUserActions={disableUserActions}
|
||||
disableAddIssue={isSubscribedIssues}
|
||||
viewProps={viewProps}
|
||||
/>
|
||||
{isCollapsed && (
|
||||
@ -150,41 +153,41 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
</div>
|
||||
{selectedGroup !== "created_by" && (
|
||||
<div>
|
||||
{type === "issue" ? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
|
||||
onClick={addIssueToGroup}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
) : (
|
||||
!disableUserActions && (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem onClick={addIssueToGroup}>
|
||||
Create new
|
||||
</CustomMenu.MenuItem>
|
||||
{openIssuesListModal && (
|
||||
<CustomMenu.MenuItem onClick={openIssuesListModal}>
|
||||
Add an existing issue
|
||||
{type === "issue"
|
||||
? !isSubscribedIssues && (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
|
||||
onClick={addIssueToGroup}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
)
|
||||
: !disableUserActions && (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem onClick={addIssueToGroup}>
|
||||
Create new
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
)
|
||||
)}
|
||||
{openIssuesListModal && (
|
||||
<CustomMenu.MenuItem onClick={openIssuesListModal}>
|
||||
Add an existing issue
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -350,7 +350,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
{properties.labels && issue.labels.length > 0 && (
|
||||
<ViewIssueLabel issue={issue} maxRender={2} />
|
||||
<ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
|
||||
)}
|
||||
{properties.assignee && (
|
||||
<ViewAssigneeSelect
|
||||
|
@ -36,9 +36,21 @@ import { LayerDiagonalIcon } from "components/icons";
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
import { handleIssuesMutation } from "constants/issue";
|
||||
// types
|
||||
import { ICurrentUserResponse, IIssue, IIssueViewProps, ISubIssueResponse, UserAuth } from "types";
|
||||
import {
|
||||
ICurrentUserResponse,
|
||||
IIssue,
|
||||
IIssueViewProps,
|
||||
ISubIssueResponse,
|
||||
IUserProfileProjectSegregation,
|
||||
UserAuth,
|
||||
} from "types";
|
||||
// fetch-keys
|
||||
import { CYCLE_DETAILS, MODULE_DETAILS, SUB_ISSUES } from "constants/fetch-keys";
|
||||
import {
|
||||
CYCLE_DETAILS,
|
||||
MODULE_DETAILS,
|
||||
SUB_ISSUES,
|
||||
USER_PROFILE_PROJECT_SEGREGATION,
|
||||
} from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
type?: string;
|
||||
@ -74,7 +86,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
const [contextMenuPosition, setContextMenuPosition] = useState<React.MouseEvent | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
const { workspaceSlug, projectId, cycleId, moduleId, userId } = router.query;
|
||||
const isArchivedIssues = router.pathname.includes("archived-issues");
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
@ -126,6 +138,11 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
.then(() => {
|
||||
mutateIssues();
|
||||
|
||||
if (userId)
|
||||
mutate<IUserProfileProjectSegregation>(
|
||||
USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
||||
);
|
||||
|
||||
if (cycleId) mutate(CYCLE_DETAILS(cycleId as string));
|
||||
if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
|
||||
});
|
||||
@ -134,6 +151,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
workspaceSlug,
|
||||
cycleId,
|
||||
moduleId,
|
||||
userId,
|
||||
groupTitle,
|
||||
index,
|
||||
selectedGroup,
|
||||
@ -261,7 +279,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
isNotAllowed={isNotAllowed}
|
||||
/>
|
||||
)}
|
||||
{properties.labels && <ViewIssueLabel issue={issue} maxRender={3} />}
|
||||
{properties.labels && <ViewIssueLabel labelDetails={issue.label_details} maxRender={3} />}
|
||||
{properties.assignee && (
|
||||
<ViewAssigneeSelect
|
||||
issue={issue}
|
||||
|
@ -60,6 +60,7 @@ export const SingleList: React.FC<Props> = ({
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const isArchivedIssues = router.pathname.includes("archived-issues");
|
||||
const isSubscribedIssues = router.pathname.includes("subscribed");
|
||||
|
||||
const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
|
||||
|
||||
@ -180,13 +181,15 @@ export const SingleList: React.FC<Props> = ({
|
||||
{isArchivedIssues ? (
|
||||
""
|
||||
) : type === "issue" ? (
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={addIssueToGroup}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</button>
|
||||
!isSubscribedIssues && (
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={addIssueToGroup}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</button>
|
||||
)
|
||||
) : disableUserActions ? (
|
||||
""
|
||||
) : (
|
||||
|
@ -323,7 +323,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
||||
)}
|
||||
{properties.labels && (
|
||||
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
|
||||
<ViewIssueLabel issue={issue} maxRender={1} />
|
||||
<ViewIssueLabel labelDetails={issue.label_details} maxRender={1} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -31,6 +31,8 @@ import {
|
||||
CompletedStateIcon,
|
||||
} from "components/icons";
|
||||
import { StarIcon } from "@heroicons/react/24/outline";
|
||||
// components
|
||||
import { ViewIssueLabel } from "components/issues";
|
||||
// helpers
|
||||
import {
|
||||
getDateRangeStatus,
|
||||
@ -441,7 +443,10 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
issues.map((issue) => (
|
||||
<div
|
||||
key={issue.id}
|
||||
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5"
|
||||
onClick={() =>
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)
|
||||
}
|
||||
className="flex flex-wrap cursor-pointer rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div>
|
||||
@ -474,27 +479,7 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
>
|
||||
{getPriorityIcon(issue.priority, "text-sm")}
|
||||
</div>
|
||||
{issue.label_details.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{issue.label_details.map((label) => (
|
||||
<span
|
||||
key={label.id}
|
||||
className="group flex items-center gap-1 rounded-2xl border border-custom-border-200 px-2 py-0.5 text-xs text-custom-text-200"
|
||||
>
|
||||
<span
|
||||
className="h-1.5 w-1.5 rounded-full"
|
||||
style={{
|
||||
backgroundColor:
|
||||
label?.color && label.color !== "" ? label.color : "#000",
|
||||
}}
|
||||
/>
|
||||
{label.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
|
||||
<div className={`flex items-center gap-2 text-custom-text-200`}>
|
||||
{issue.assignees &&
|
||||
issue.assignees.length > 0 &&
|
||||
|
@ -23,7 +23,13 @@ const tabOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorChange }) => {
|
||||
const EmojiIconPicker: React.FC<Props> = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
onIconColorChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [openColorPicker, setOpenColorPicker] = useState(false);
|
||||
const [activeColor, setActiveColor] = useState<string>("rgb(var(--color-text-200))");
|
||||
@ -40,7 +46,11 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorC
|
||||
|
||||
return (
|
||||
<Popover className="relative z-[1]">
|
||||
<Popover.Button onClick={() => setIsOpen((prev) => !prev)} className="outline-none">
|
||||
<Popover.Button
|
||||
onClick={() => setIsOpen((prev) => !prev)}
|
||||
className="outline-none"
|
||||
disabled={disabled}
|
||||
>
|
||||
{label}
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
|
@ -10,4 +10,5 @@ export type Props = {
|
||||
}
|
||||
) => void;
|
||||
onIconColorChange?: (data: any) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
@ -33,16 +33,16 @@ type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
data: IIssue | null;
|
||||
onSubmit?: () => Promise<void>;
|
||||
user: ICurrentUserResponse | undefined;
|
||||
onSubmit?: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const DeleteIssueModal: React.FC<Props> = ({
|
||||
isOpen,
|
||||
handleClose,
|
||||
data,
|
||||
onSubmit,
|
||||
user,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
@ -138,6 +138,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
||||
console.log(error);
|
||||
setIsDeleteLoading(false);
|
||||
});
|
||||
if (onSubmit) await onSubmit();
|
||||
};
|
||||
|
||||
const handleArchivedIssueDeletion = async () => {
|
||||
|
@ -6,16 +6,16 @@ import { Tooltip } from "components/ui";
|
||||
import { IIssue } from "types";
|
||||
|
||||
type Props = {
|
||||
issue: IIssue;
|
||||
labelDetails: any[];
|
||||
maxRender?: number;
|
||||
};
|
||||
|
||||
export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => (
|
||||
export const ViewIssueLabel: React.FC<Props> = ({ labelDetails, maxRender = 1 }) => (
|
||||
<>
|
||||
{issue.label_details.length > 0 ? (
|
||||
issue.label_details.length <= maxRender ? (
|
||||
{labelDetails.length > 0 ? (
|
||||
labelDetails.length <= maxRender ? (
|
||||
<>
|
||||
{issue.label_details.map((label, index) => (
|
||||
{labelDetails.map((label) => (
|
||||
<div
|
||||
key={label.id}
|
||||
className="flex cursor-default items-center flex-shrink-0 rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm"
|
||||
@ -39,11 +39,11 @@ export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => (
|
||||
<Tooltip
|
||||
position="top"
|
||||
tooltipHeading="Labels"
|
||||
tooltipContent={issue.label_details.map((l) => l.name).join(", ")}
|
||||
tooltipContent={labelDetails.map((l) => l.name).join(", ")}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 text-custom-text-200">
|
||||
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-custom-primary" />
|
||||
{`${issue.label_details.length} Labels`}
|
||||
{`${labelDetails.length} Labels`}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -93,7 +93,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
|
||||
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
||||
if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string };
|
||||
if (router.asPath.includes("my-issues"))
|
||||
if (router.asPath.includes("my-issues") || router.asPath.includes("assigned"))
|
||||
prePopulateData = {
|
||||
...prePopulateData,
|
||||
assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""],
|
||||
|
@ -123,7 +123,7 @@ export const MyIssuesViewOptions: React.FC = () => {
|
||||
: "text-custom-sidebar-text-200"
|
||||
}`}
|
||||
>
|
||||
View
|
||||
Display
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
|
||||
|
@ -234,6 +234,9 @@ export const MyIssuesView: React.FC<Props> = ({
|
||||
isOpen={deleteIssueModal}
|
||||
data={issueToDelete}
|
||||
user={user}
|
||||
onSubmit={async () => {
|
||||
mutateMyIssues();
|
||||
}}
|
||||
/>
|
||||
{areFiltersApplied && (
|
||||
<>
|
||||
|
@ -149,7 +149,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
||||
: "text-custom-sidebar-text-200"
|
||||
}`}
|
||||
>
|
||||
View
|
||||
Display
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
|
||||
|
@ -248,6 +248,9 @@ export const ProfileIssuesView = () => {
|
||||
isOpen={deleteIssueModal}
|
||||
data={issueToDelete}
|
||||
user={user}
|
||||
onSubmit={async () => {
|
||||
mutateProfileIssues();
|
||||
}}
|
||||
/>
|
||||
{areFiltersApplied && (
|
||||
<>
|
||||
|
@ -6,78 +6,88 @@ import { Loader, Tooltip } from "components/ui";
|
||||
import { InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IUserWorkspaceDashboard } from "types";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
type Props = {
|
||||
data: IUserWorkspaceDashboard | undefined;
|
||||
};
|
||||
|
||||
export const IssuesStats: React.FC<Props> = ({ data }) => (
|
||||
<div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3">
|
||||
<div className="grid grid-cols-1 divide-y divide-custom-border-200 border-b border-custom-border-200 lg:border-r lg:border-b-0">
|
||||
<div className="flex">
|
||||
<div className="basis-1/2 p-4">
|
||||
<h4 className="text-sm">Issues assigned to you</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.assigned_issues_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
export const IssuesStats: React.FC<Props> = ({ data }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
return (
|
||||
<div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3">
|
||||
<div className="grid grid-cols-1 divide-y divide-custom-border-200 border-b border-custom-border-200 lg:border-r lg:border-b-0">
|
||||
<div className="flex">
|
||||
<div className="basis-1/2 p-4">
|
||||
<h4 className="text-sm">Issues assigned to you</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => router.push(`/${workspaceSlug}/me/my-issues`)}
|
||||
>
|
||||
{data.assigned_issues_count}
|
||||
</div>
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
<div className="basis-1/2 border-l border-custom-border-200 p-4">
|
||||
<h4 className="text-sm">Pending issues</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.pending_issues_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div className="basis-1/2 border-l border-custom-border-200 p-4">
|
||||
<h4 className="text-sm">Pending issues</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.pending_issues_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
<div className="flex">
|
||||
<div className="basis-1/2 p-4">
|
||||
<h4 className="text-sm">Completed issues</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.completed_issues_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
<div className="basis-1/2 border-l border-custom-border-200 p-4">
|
||||
<h4 className="text-sm">Issues due by this week</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.issues_due_week_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="basis-1/2 p-4">
|
||||
<h4 className="text-sm">Completed issues</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.completed_issues_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
<div className="basis-1/2 border-l border-custom-border-200 p-4">
|
||||
<h4 className="text-sm">Issues due by this week</h4>
|
||||
<h5 className="mt-2 text-2xl font-semibold">
|
||||
{data ? (
|
||||
data.issues_due_week_count
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="25px" width="50%" />
|
||||
</Loader>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
<div className="p-4 lg:col-span-2">
|
||||
<h3 className="mb-2 font-semibold capitalize flex items-center gap-2">
|
||||
Activity Graph
|
||||
<Tooltip
|
||||
tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
|
||||
className="w-72 border border-custom-border-200"
|
||||
>
|
||||
<InformationCircleIcon className="h-3 w-3" />
|
||||
</Tooltip>
|
||||
</h3>
|
||||
<ActivityGraph activities={data?.issue_activities} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 lg:col-span-2">
|
||||
<h3 className="mb-2 font-semibold capitalize flex items-center gap-2">
|
||||
Activity Graph
|
||||
<Tooltip
|
||||
tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
|
||||
className="w-72 border border-custom-border-200"
|
||||
>
|
||||
<InformationCircleIcon className="h-3 w-3" />
|
||||
</Tooltip>
|
||||
</h3>
|
||||
<ActivityGraph activities={data?.issue_activities} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
@ -476,7 +476,7 @@ const SinglePage: NextPage = () => {
|
||||
: "text-custom-sidebar-text-200"
|
||||
}`}
|
||||
>
|
||||
View
|
||||
Display
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
|
||||
|
@ -187,7 +187,7 @@ const GeneralSettings: NextPage = () => {
|
||||
/>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="p-8">
|
||||
<SettingsHeader />
|
||||
<div className="space-y-8 sm:space-y-12">
|
||||
<div className="space-y-8 sm:space-y-12 opacity-60">
|
||||
<div className="grid grid-cols-12 items-start gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Icon & Name</h4>
|
||||
@ -206,6 +206,7 @@ const GeneralSettings: NextPage = () => {
|
||||
label={value ? renderEmoji(value) : "Icon"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@ -225,6 +226,7 @@ const GeneralSettings: NextPage = () => {
|
||||
validations={{
|
||||
required: "Name is required",
|
||||
}}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
) : (
|
||||
<Loader>
|
||||
@ -248,6 +250,7 @@ const GeneralSettings: NextPage = () => {
|
||||
placeholder="Enter project description"
|
||||
validations={{}}
|
||||
className="min-h-[46px] text-sm"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
) : (
|
||||
<Loader className="w-full">
|
||||
@ -279,6 +282,7 @@ const GeneralSettings: NextPage = () => {
|
||||
setValue("cover_image", imageUrl);
|
||||
}}
|
||||
value={watch("cover_image")}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -319,6 +323,7 @@ const GeneralSettings: NextPage = () => {
|
||||
message: "Identifier must at most be of 5 characters",
|
||||
},
|
||||
}}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
) : (
|
||||
<Loader>
|
||||
@ -343,6 +348,7 @@ const GeneralSettings: NextPage = () => {
|
||||
onChange={onChange}
|
||||
label={currentNetwork?.label ?? "Select network"}
|
||||
input
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{NETWORK_CHOICES.map((network) => (
|
||||
<CustomSelect.Option key={network.key} value={network.key}>
|
||||
@ -359,44 +365,48 @@ const GeneralSettings: NextPage = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:text-right">
|
||||
{projectDetails ? (
|
||||
<SecondaryButton type="submit" loading={isSubmitting} disabled={!isAdmin}>
|
||||
{isSubmitting ? "Updating Project..." : "Update Project"}
|
||||
</SecondaryButton>
|
||||
) : (
|
||||
<Loader className="mt-2 w-full">
|
||||
<Loader.Item height="34px" width="100px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
{memberDetails?.role === 20 && (
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Danger Zone</h4>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
The danger zone of the project delete page is a critical area that requires
|
||||
careful consideration and attention. When deleting a project, all of the data and
|
||||
resources within that project will be permanently removed and cannot be recovered.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
|
||||
{isAdmin && (
|
||||
<>
|
||||
<div className="sm:text-right">
|
||||
{projectDetails ? (
|
||||
<div>
|
||||
<DangerButton
|
||||
onClick={() => setSelectedProject(projectDetails.id ?? null)}
|
||||
outline
|
||||
>
|
||||
Delete Project
|
||||
</DangerButton>
|
||||
</div>
|
||||
<SecondaryButton type="submit" loading={isSubmitting} disabled={!isAdmin}>
|
||||
{isSubmitting ? "Updating Project..." : "Update Project"}
|
||||
</SecondaryButton>
|
||||
) : (
|
||||
<Loader className="mt-2 w-full">
|
||||
<Loader.Item height="46px" width="100px" />
|
||||
<Loader.Item height="34px" width="100px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Danger Zone</h4>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
The danger zone of the project delete page is a critical area that requires
|
||||
careful consideration and attention. When deleting a project, all of the data
|
||||
and resources within that project will be permanently removed and cannot be
|
||||
recovered.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
{projectDetails ? (
|
||||
<div>
|
||||
<DangerButton
|
||||
onClick={() => setSelectedProject(projectDetails.id ?? null)}
|
||||
outline
|
||||
>
|
||||
Delete Project
|
||||
</DangerButton>
|
||||
</div>
|
||||
) : (
|
||||
<Loader className="mt-2 w-full">
|
||||
<Loader.Item height="46px" width="100px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -181,7 +181,7 @@ const WorkspaceSettings: NextPage = () => {
|
||||
<div className="p-8">
|
||||
<SettingsHeader />
|
||||
{activeWorkspace ? (
|
||||
<div className="space-y-8 sm:space-y-12">
|
||||
<div className="space-y-8 sm:space-y-12 opacity-60">
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Logo</h4>
|
||||
@ -191,7 +191,11 @@ const WorkspaceSettings: NextPage = () => {
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsImageUploadModalOpen(true)}
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
||||
<div className="relative mx-auto flex h-12 w-12">
|
||||
<img
|
||||
@ -206,23 +210,25 @@ const WorkspaceSettings: NextPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex gap-4">
|
||||
<SecondaryButton
|
||||
onClick={() => {
|
||||
setIsImageUploadModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{isImageUploading ? "Uploading..." : "Upload"}
|
||||
</SecondaryButton>
|
||||
{activeWorkspace.logo && activeWorkspace.logo !== "" && (
|
||||
<DangerButton
|
||||
onClick={() => handleDelete(activeWorkspace.logo)}
|
||||
loading={isImageRemoving}
|
||||
{isAdmin && (
|
||||
<div className="flex gap-4">
|
||||
<SecondaryButton
|
||||
onClick={() => {
|
||||
setIsImageUploadModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{isImageRemoving ? "Removing..." : "Remove"}
|
||||
</DangerButton>
|
||||
)}
|
||||
</div>
|
||||
{isImageUploading ? "Uploading..." : "Upload"}
|
||||
</SecondaryButton>
|
||||
{activeWorkspace.logo && activeWorkspace.logo !== "" && (
|
||||
<DangerButton
|
||||
onClick={() => handleDelete(activeWorkspace.logo)}
|
||||
loading={isImageRemoving}
|
||||
>
|
||||
{isImageRemoving ? "Removing..." : "Remove"}
|
||||
</DangerButton>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -288,6 +294,7 @@ const WorkspaceSettings: NextPage = () => {
|
||||
message: "Workspace name should not exceed 80 characters",
|
||||
},
|
||||
}}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -309,6 +316,7 @@ const WorkspaceSettings: NextPage = () => {
|
||||
}
|
||||
width="w-full"
|
||||
input
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{ORGANIZATION_SIZE?.map((item) => (
|
||||
<CustomSelect.Option key={item} value={item}>
|
||||
@ -320,32 +328,35 @@ const WorkspaceSettings: NextPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:text-right">
|
||||
<SecondaryButton
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{isSubmitting ? "Updating..." : "Update Workspace"}
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
{memberDetails?.role === 20 && (
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Danger Zone</h4>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
The danger zone of the workspace delete page is a critical area that requires
|
||||
careful consideration and attention. When deleting a workspace, all of the data
|
||||
and resources within that workspace will be permanently removed and cannot be
|
||||
recovered.
|
||||
</p>
|
||||
|
||||
{isAdmin && (
|
||||
<>
|
||||
<div className="sm:text-right">
|
||||
<SecondaryButton
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{isSubmitting ? "Updating..." : "Update Workspace"}
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<DangerButton onClick={() => setIsOpen(true)} outline>
|
||||
Delete the workspace
|
||||
</DangerButton>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Danger Zone</h4>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
The danger zone of the workspace delete page is a critical area that requires
|
||||
careful consideration and attention. When deleting a workspace, all of the
|
||||
data and resources within that workspace will be permanently removed and
|
||||
cannot be recovered.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<DangerButton onClick={() => setIsOpen(true)} outline>
|
||||
Delete the workspace
|
||||
</DangerButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
Loading…
Reference in New Issue
Block a user