forked from github/plane
Merge pull request #669 from makeplane/develop
promote: develop to stage-release
This commit is contained in:
commit
93ba04aebc
2
.github/workflows/push-image-frontend.yml
vendored
2
.github/workflows/push-image-frontend.yml
vendored
@ -5,6 +5,8 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'develop'
|
- 'develop'
|
||||||
- 'master'
|
- 'master'
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_push_frontend:
|
build_push_frontend:
|
||||||
|
@ -9,9 +9,11 @@ import issuesService from "services/issues.service";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
|
// component
|
||||||
|
import { Avatar } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, PlusIcon } from "@heroicons/react/24/outline";
|
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||||
import { getStateGroupIcon } from "components/icons";
|
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
@ -89,9 +91,39 @@ export const BoardHeader: React.FC<Props> = ({
|
|||||||
return title;
|
return title;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getGroupIcon = () => {
|
||||||
|
let icon;
|
||||||
|
|
||||||
|
switch (selectedGroup) {
|
||||||
|
case "state":
|
||||||
|
icon = currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor);
|
||||||
|
break;
|
||||||
|
case "priority":
|
||||||
|
icon = getPriorityIcon(groupTitle, "h-[18px] w-[18px] flex items-center");
|
||||||
|
break;
|
||||||
|
case "labels":
|
||||||
|
const labelColor =
|
||||||
|
issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000";
|
||||||
|
icon = (
|
||||||
|
<span
|
||||||
|
className="h-[18px] w-[18px] flex-shrink-0 rounded-full"
|
||||||
|
style={{ backgroundColor: labelColor }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "created_by":
|
||||||
|
const member = members?.find((member) => member.member.id === groupTitle)?.member;
|
||||||
|
icon = <Avatar user={member} height="24px" width="24px" fontSize="12px" />;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex justify-between px-1 ${
|
className={`flex justify-between items-center px-1 ${
|
||||||
!isCollapsed ? "flex-col rounded-md border bg-gray-50" : ""
|
!isCollapsed ? "flex-col rounded-md border bg-gray-50" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -101,7 +133,7 @@ export const BoardHeader: React.FC<Props> = ({
|
|||||||
!isCollapsed ? "mb-2 flex-col gap-y-2 py-2" : ""
|
!isCollapsed ? "mb-2 flex-col gap-y-2 py-2" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor)}
|
<span className="flex items-center">{getGroupIcon()}</span>
|
||||||
<h2
|
<h2
|
||||||
className="text-lg font-semibold capitalize"
|
className="text-lg font-semibold capitalize"
|
||||||
style={{
|
style={{
|
||||||
|
277
apps/app/components/core/feeds.tsx
Normal file
277
apps/app/components/core/feeds.tsx
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
// icons
|
||||||
|
import {
|
||||||
|
ArrowTopRightOnSquareIcon,
|
||||||
|
CalendarDaysIcon,
|
||||||
|
ChartBarIcon,
|
||||||
|
ChatBubbleBottomCenterTextIcon,
|
||||||
|
ChatBubbleLeftEllipsisIcon,
|
||||||
|
RectangleGroupIcon,
|
||||||
|
Squares2X2Icon,
|
||||||
|
UserIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { BlockedIcon, BlockerIcon, CyclesIcon, TagIcon, UserGroupIcon } from "components/icons";
|
||||||
|
// helpers
|
||||||
|
import { renderShortNumericDateFormat, timeAgo } from "helpers/date-time.helper";
|
||||||
|
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||||
|
// types
|
||||||
|
import RemirrorRichTextEditor from "components/rich-text-editor";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const activityDetails: {
|
||||||
|
[key: string]: {
|
||||||
|
message?: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
assignee: {
|
||||||
|
message: "removed the assignee",
|
||||||
|
icon: <UserGroupIcon className="h-3 w-3" color="#6b7280" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
assignees: {
|
||||||
|
message: "added a new assignee",
|
||||||
|
icon: <UserGroupIcon className="h-3 w-3" color="#6b7280" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
blocks: {
|
||||||
|
message: "marked this issue being blocked by",
|
||||||
|
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
||||||
|
},
|
||||||
|
blocking: {
|
||||||
|
message: "marked this issue is blocking",
|
||||||
|
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
||||||
|
},
|
||||||
|
cycles: {
|
||||||
|
message: "set the cycle to",
|
||||||
|
icon: <CyclesIcon height="12" width="12" color="#6b7280" />,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
icon: <TagIcon height="12" width="12" color="#6b7280" />,
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
message: "set the module to",
|
||||||
|
icon: <RectangleGroupIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
message: "set the state to",
|
||||||
|
icon: <Squares2X2Icon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
priority: {
|
||||||
|
message: "set the priority to",
|
||||||
|
icon: <ChartBarIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
message: "set the name to",
|
||||||
|
icon: <ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
message: "updated the description.",
|
||||||
|
icon: <ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
target_date: {
|
||||||
|
message: "set the due date to",
|
||||||
|
icon: <CalendarDaysIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
message: "set the parent to",
|
||||||
|
icon: <UserIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Feeds: React.FC<any> = ({ activities }) => (
|
||||||
|
<div>
|
||||||
|
<ul role="list" className="-mb-4">
|
||||||
|
{activities.map((activity: any, activityIdx: number) => {
|
||||||
|
// determines what type of action is performed
|
||||||
|
let action = activityDetails[activity.field as keyof typeof activityDetails]?.message;
|
||||||
|
if (activity.field === "labels") {
|
||||||
|
action = activity.new_value !== "" ? "added a new label" : "removed the label";
|
||||||
|
} else if (activity.field === "blocking") {
|
||||||
|
action =
|
||||||
|
activity.new_value !== ""
|
||||||
|
? "marked this issue is blocking"
|
||||||
|
: "removed the issue from blocking";
|
||||||
|
} else if (activity.field === "blocks") {
|
||||||
|
action =
|
||||||
|
activity.new_value !== "" ? "marked this issue being blocked by" : "removed blocker";
|
||||||
|
} else if (activity.field === "target_date") {
|
||||||
|
action =
|
||||||
|
activity.new_value && activity.new_value !== ""
|
||||||
|
? "set the due date to"
|
||||||
|
: "removed the due date";
|
||||||
|
} else if (activity.field === "parent") {
|
||||||
|
action =
|
||||||
|
activity.new_value && activity.new_value !== ""
|
||||||
|
? "set the parent to"
|
||||||
|
: "removed the parent";
|
||||||
|
} else if (activity.field === "priority") {
|
||||||
|
action =
|
||||||
|
activity.new_value && activity.new_value !== ""
|
||||||
|
? "set the priority to"
|
||||||
|
: "removed the priority";
|
||||||
|
} else if (activity.field === "description") {
|
||||||
|
action = "updated the";
|
||||||
|
}
|
||||||
|
// for values that are after the action clause
|
||||||
|
let value: any = activity.new_value ? activity.new_value : activity.old_value;
|
||||||
|
if (
|
||||||
|
activity.verb === "created" &&
|
||||||
|
activity.field !== "cycles" &&
|
||||||
|
activity.field !== "modules"
|
||||||
|
) {
|
||||||
|
const { workspace_detail, project, issue } = activity;
|
||||||
|
value = (
|
||||||
|
<span className="text-gray-600">
|
||||||
|
created{" "}
|
||||||
|
<Link href={`/${workspace_detail.slug}/projects/${project}/issues/${issue}`}>
|
||||||
|
<a className="inline-flex items-center hover:underline">
|
||||||
|
this issue. <ArrowTopRightOnSquareIcon className="h-3.5 w-3.5 ml-1" />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (activity.field === "state") {
|
||||||
|
value = activity.new_value ? addSpaceIfCamelCase(activity.new_value) : "None";
|
||||||
|
} else if (activity.field === "labels") {
|
||||||
|
let name;
|
||||||
|
let id = "#000000";
|
||||||
|
if (activity.new_value !== "") {
|
||||||
|
name = activity.new_value;
|
||||||
|
id = activity.new_identifier ? activity.new_identifier : id;
|
||||||
|
} else {
|
||||||
|
name = activity.old_value;
|
||||||
|
id = activity.old_identifier ? activity.old_identifier : id;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = name;
|
||||||
|
} else if (activity.field === "assignees") {
|
||||||
|
value = activity.new_value;
|
||||||
|
} else if (activity.field === "target_date") {
|
||||||
|
const date =
|
||||||
|
activity.new_value && activity.new_value !== ""
|
||||||
|
? activity.new_value
|
||||||
|
: activity.old_value;
|
||||||
|
value = renderShortNumericDateFormat(date as string);
|
||||||
|
} else if (activity.field === "description") {
|
||||||
|
value = "description";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.field === "comment") {
|
||||||
|
return (
|
||||||
|
<div key={activity.id}>
|
||||||
|
<div className="relative flex items-start space-x-3">
|
||||||
|
<div className="relative px-1">
|
||||||
|
{activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
|
||||||
|
<Image
|
||||||
|
src={activity.actor_detail.avatar}
|
||||||
|
alt={activity.actor_detail.first_name}
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
className="grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}
|
||||||
|
>
|
||||||
|
{activity.actor_detail.first_name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="absolute -bottom-0.5 -right-1 rounded-tl bg-white px-0.5 py-px">
|
||||||
|
<ChatBubbleLeftEllipsisIcon
|
||||||
|
className="h-3.5 w-3.5 text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs">
|
||||||
|
{activity.actor_detail.first_name}
|
||||||
|
{activity.actor_detail.is_bot ? "Bot" : " " + activity.actor_detail.last_name}
|
||||||
|
</div>
|
||||||
|
<p className="mt-0.5 text-xs text-gray-500">
|
||||||
|
Commented {timeAgo(activity.created_at)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="issue-comments-section p-0">
|
||||||
|
<RemirrorRichTextEditor
|
||||||
|
value={
|
||||||
|
activity.new_value && activity.new_value !== ""
|
||||||
|
? activity.new_value
|
||||||
|
: activity.old_value
|
||||||
|
}
|
||||||
|
editable={false}
|
||||||
|
onBlur={() => ({})}
|
||||||
|
noBorder
|
||||||
|
customClassName="text-xs bg-gray-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("field" in activity && activity.field !== "updated_by") {
|
||||||
|
return (
|
||||||
|
<li key={activity.id}>
|
||||||
|
<div className="relative pb-1">
|
||||||
|
{activities.length > 1 && activityIdx !== activities.length - 1 ? (
|
||||||
|
<span
|
||||||
|
className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-gray-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="relative flex items-start space-x-2">
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<div className="relative px-1.5">
|
||||||
|
<div className="mt-1.5">
|
||||||
|
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-gray-100 ring-white">
|
||||||
|
{activity.field ? (
|
||||||
|
activityDetails[activity.field as keyof typeof activityDetails]?.icon
|
||||||
|
) : activity.actor_detail.avatar &&
|
||||||
|
activity.actor_detail.avatar !== "" ? (
|
||||||
|
<Image
|
||||||
|
src={activity.actor_detail.avatar}
|
||||||
|
alt={activity.actor_detail.first_name}
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
className="rounded-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white`}
|
||||||
|
>
|
||||||
|
{activity.actor_detail.first_name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1 py-3">
|
||||||
|
<div className="text-xs text-gray-500">
|
||||||
|
<span className="text-gray font-medium">
|
||||||
|
{activity.actor_detail.first_name}
|
||||||
|
{activity.actor_detail.is_bot
|
||||||
|
? " Bot"
|
||||||
|
: " " + activity.actor_detail.last_name}
|
||||||
|
</span>
|
||||||
|
<span> {action} </span>
|
||||||
|
<span className="text-xs font-medium text-gray-900"> {value} </span>
|
||||||
|
<span className="whitespace-nowrap">{timeAgo(activity.created_at)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
@ -131,11 +131,11 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
|
|||||||
? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100"
|
? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100"
|
||||||
: priority === "low"
|
: priority === "low"
|
||||||
? "bg-green-100 text-green-500 hover:bg-green-100"
|
? "bg-green-100 text-green-500 hover:bg-green-100"
|
||||||
: "bg-gray-100"
|
: "bg-gray-100 text-gray-700 hover:bg-gray-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{getPriorityIcon(priority)}</span>
|
<span>{getPriorityIcon(priority)}</span>
|
||||||
<span>{priority}</span>
|
<span>{priority ? priority : "None"}</span>
|
||||||
<span
|
<span
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -11,3 +11,4 @@ export * from "./link-modal";
|
|||||||
export * from "./not-authorized-view";
|
export * from "./not-authorized-view";
|
||||||
export * from "./image-picker-popover";
|
export * from "./image-picker-popover";
|
||||||
export * from "./filter-list";
|
export * from "./filter-list";
|
||||||
|
export * from "./feeds";
|
||||||
|
@ -29,22 +29,16 @@ import {
|
|||||||
RectangleStackIcon,
|
RectangleStackIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { ExclamationIcon, getStateGroupIcon, TransferIcon } from "components/icons";
|
import { ExclamationIcon, TransferIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { getStatesList } from "helpers/state.helper";
|
import { getStatesList } from "helpers/state.helper";
|
||||||
// types
|
// types
|
||||||
import {
|
import { IIssue, IIssueFilterOptions, UserAuth } from "types";
|
||||||
CycleIssueResponse,
|
|
||||||
IIssue,
|
|
||||||
IIssueFilterOptions,
|
|
||||||
ModuleIssueResponse,
|
|
||||||
UserAuth,
|
|
||||||
} from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_ISSUES,
|
CYCLE_DETAILS,
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
MODULE_ISSUES,
|
MODULE_DETAILS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
STATE_LIST,
|
STATE_LIST,
|
||||||
@ -266,8 +260,14 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
sort_order: draggedItem.sort_order,
|
sort_order: draggedItem.sort_order,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
if (cycleId) {
|
||||||
if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
|
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
||||||
|
mutate(CYCLE_DETAILS(cycleId as string));
|
||||||
|
}
|
||||||
|
if (moduleId) {
|
||||||
|
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
||||||
|
mutate(MODULE_DETAILS(moduleId as string));
|
||||||
|
}
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -322,13 +322,9 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
const removeIssueFromCycle = useCallback(
|
const removeIssueFromCycle = useCallback(
|
||||||
(bridgeId: string) => {
|
(bridgeId: string) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||||
|
|
||||||
mutate<CycleIssueResponse[]>(
|
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
||||||
CYCLE_ISSUES(cycleId as string),
|
|
||||||
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.removeIssueFromCycle(
|
.removeIssueFromCycle(
|
||||||
@ -344,18 +340,14 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, cycleId]
|
[workspaceSlug, projectId, cycleId, params]
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeIssueFromModule = useCallback(
|
const removeIssueFromModule = useCallback(
|
||||||
(bridgeId: string) => {
|
(bridgeId: string) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
|
|
||||||
mutate<ModuleIssueResponse[]>(
|
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
||||||
MODULE_ISSUES(moduleId as string),
|
|
||||||
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
modulesService
|
modulesService
|
||||||
.removeIssueFromModule(
|
.removeIssueFromModule(
|
||||||
@ -371,7 +363,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, moduleId]
|
[workspaceSlug, projectId, moduleId, params]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTrashBox = useCallback(
|
const handleTrashBox = useCallback(
|
||||||
|
@ -12,10 +12,10 @@ import useIssuesProperties from "hooks/use-issue-properties";
|
|||||||
// components
|
// components
|
||||||
import { SingleListIssue } from "components/core";
|
import { SingleListIssue } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "components/ui";
|
import { Avatar, CustomMenu } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
import { getStateGroupIcon } from "components/icons";
|
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
@ -99,6 +99,36 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
return title;
|
return title;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getGroupIcon = () => {
|
||||||
|
let icon;
|
||||||
|
|
||||||
|
switch (selectedGroup) {
|
||||||
|
case "state":
|
||||||
|
icon = currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor);
|
||||||
|
break;
|
||||||
|
case "priority":
|
||||||
|
icon = getPriorityIcon(groupTitle, "h-[18px] w-[18px] flex items-center");
|
||||||
|
break;
|
||||||
|
case "labels":
|
||||||
|
const labelColor =
|
||||||
|
issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000";
|
||||||
|
icon = (
|
||||||
|
<span
|
||||||
|
className="h-[18px] w-[18px] flex-shrink-0 rounded-full"
|
||||||
|
style={{ backgroundColor: labelColor }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "created_by":
|
||||||
|
const member = members?.find((member) => member.member.id === groupTitle)?.member;
|
||||||
|
icon = <Avatar user={member} height="24px" width="24px" fontSize="12px" />;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Disclosure key={groupTitle} as="div" defaultOpen>
|
<Disclosure key={groupTitle} as="div" defaultOpen>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
@ -110,12 +140,8 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<Disclosure.Button>
|
<Disclosure.Button>
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex items-center gap-x-3">
|
||||||
{selectedGroup !== null && selectedGroup === "state" ? (
|
{selectedGroup !== null && (
|
||||||
<span>
|
<span className="flex items-center">{getGroupIcon()}</span>
|
||||||
{currentState && getStateGroupIcon(currentState.group, "16", "16", bgColor)}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
{selectedGroup !== null ? (
|
{selectedGroup !== null ? (
|
||||||
<h2 className="text-base font-semibold capitalize leading-6 text-gray-800">
|
<h2 className="text-base font-semibold capitalize leading-6 text-gray-800">
|
||||||
|
@ -80,6 +80,7 @@ const ProgressChart: React.FC<Props> = ({ issues, start, end }) => {
|
|||||||
tick={{ fontSize: "12px", fill: "#1f2937" }}
|
tick={{ fontSize: "12px", fill: "#1f2937" }}
|
||||||
tickSize={10}
|
tickSize={10}
|
||||||
minTickGap={10}
|
minTickGap={10}
|
||||||
|
allowDecimals={false}
|
||||||
/>
|
/>
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip />} />
|
||||||
<Area
|
<Area
|
||||||
|
@ -77,6 +77,23 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
|||||||
await cycleService
|
await cycleService
|
||||||
.updateCycle(workspaceSlug as string, projectId as string, cycleId, payload)
|
.updateCycle(workspaceSlug as string, projectId as string, cycleId, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
switch (getDateRangeStatus(data?.start_date, data?.end_date)) {
|
||||||
|
case "completed":
|
||||||
|
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
||||||
|
break;
|
||||||
|
case "current":
|
||||||
|
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
||||||
|
break;
|
||||||
|
case "upcoming":
|
||||||
|
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
getDateRangeStatus(data?.start_date, data?.end_date) !=
|
||||||
|
getDateRangeStatus(res.start_date, res.end_date)
|
||||||
|
) {
|
||||||
switch (getDateRangeStatus(res.start_date, res.end_date)) {
|
switch (getDateRangeStatus(res.start_date, res.end_date)) {
|
||||||
case "completed":
|
case "completed":
|
||||||
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
||||||
@ -90,6 +107,8 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
|||||||
default:
|
default:
|
||||||
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
|
@ -90,16 +90,13 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
const startDate = new Date(cycle.start_date ?? "");
|
const startDate = new Date(cycle.start_date ?? "");
|
||||||
|
|
||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug && !projectId && !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
cyclesService
|
|
||||||
.addCycleToFavorites(workspaceSlug as string, projectId as string, {
|
|
||||||
cycle: cycle.id,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
||||||
|
|
||||||
if (cycleStatus === "current" || cycleStatus === "upcoming")
|
switch (cycleStatus) {
|
||||||
|
case "current":
|
||||||
|
case "upcoming":
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
mutate<CurrentAndUpcomingCyclesResponse>(
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
||||||
(prevData) => ({
|
(prevData) => ({
|
||||||
@ -114,7 +111,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
else if (cycleStatus === "completed")
|
break;
|
||||||
|
case "completed":
|
||||||
mutate<CompletedCyclesResponse>(
|
mutate<CompletedCyclesResponse>(
|
||||||
CYCLE_COMPLETE_LIST(projectId as string),
|
CYCLE_COMPLETE_LIST(projectId as string),
|
||||||
(prevData) => ({
|
(prevData) => ({
|
||||||
@ -125,7 +123,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
else
|
break;
|
||||||
|
case "draft":
|
||||||
mutate<DraftCyclesResponse>(
|
mutate<DraftCyclesResponse>(
|
||||||
CYCLE_DRAFT_LIST(projectId as string),
|
CYCLE_DRAFT_LIST(projectId as string),
|
||||||
(prevData) => ({
|
(prevData) => ({
|
||||||
@ -136,12 +135,12 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
setToastAlert({
|
cyclesService
|
||||||
type: "success",
|
.addCycleToFavorites(workspaceSlug as string, projectId as string, {
|
||||||
title: "Success!",
|
cycle: cycle.id,
|
||||||
message: "Successfully added the cycle to favorites.",
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -153,14 +152,13 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = () => {
|
const handleRemoveFromFavorites = () => {
|
||||||
if (!workspaceSlug || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
cyclesService
|
|
||||||
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
|
|
||||||
.then(() => {
|
|
||||||
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
||||||
|
|
||||||
if (cycleStatus === "current" || cycleStatus === "upcoming")
|
switch (cycleStatus) {
|
||||||
|
case "current":
|
||||||
|
case "upcoming":
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
mutate<CurrentAndUpcomingCyclesResponse>(
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
||||||
(prevData) => ({
|
(prevData) => ({
|
||||||
@ -175,7 +173,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
else if (cycleStatus === "completed")
|
break;
|
||||||
|
case "completed":
|
||||||
mutate<CompletedCyclesResponse>(
|
mutate<CompletedCyclesResponse>(
|
||||||
CYCLE_COMPLETE_LIST(projectId as string),
|
CYCLE_COMPLETE_LIST(projectId as string),
|
||||||
(prevData) => ({
|
(prevData) => ({
|
||||||
@ -186,7 +185,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
else
|
break;
|
||||||
|
case "draft":
|
||||||
mutate<DraftCyclesResponse>(
|
mutate<DraftCyclesResponse>(
|
||||||
CYCLE_DRAFT_LIST(projectId as string),
|
CYCLE_DRAFT_LIST(projectId as string),
|
||||||
(prevData) => ({
|
(prevData) => ({
|
||||||
@ -197,13 +197,11 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
setToastAlert({
|
cyclesService
|
||||||
type: "success",
|
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
|
||||||
title: "Success!",
|
|
||||||
message: "Successfully removed the cycle from favorites.",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -12,7 +12,7 @@ import cyclesService from "services/cycles.service";
|
|||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
//icons
|
//icons
|
||||||
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
import { ContrastIcon, CyclesIcon } from "components/icons";
|
import { ContrastIcon, CyclesIcon, ExclamationIcon } from "components/icons";
|
||||||
// fetch-key
|
// fetch-key
|
||||||
import { CYCLE_INCOMPLETE_LIST } from "constants/fetch-keys";
|
import { CYCLE_INCOMPLETE_LIST } from "constants/fetch-keys";
|
||||||
// types
|
// types
|
||||||
@ -31,7 +31,6 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
|
||||||
const transferIssue = async (payload: any) => {
|
const transferIssue = async (payload: any) => {
|
||||||
await cyclesService
|
await cyclesService
|
||||||
.transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload)
|
.transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload)
|
||||||
@ -39,16 +38,14 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Issues transfered successfully",
|
title: "Issues transfered successfully",
|
||||||
message:
|
message: "Issues have been transferred successfully",
|
||||||
"Issues have been transferred successfully",
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message:
|
message: "Issues cannot be transfer. Please try again.",
|
||||||
"Issues cannot be transfer. Please try again.",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -100,15 +97,15 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform rounded-lg bg-white p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
|
<Dialog.Panel className="relative transform rounded-lg bg-white py-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between px-5">
|
||||||
<h4 className="text-gray-700 text-base">Transfer Issues</h4>
|
<h4 className="text-gray-700 text-base">Transfer Issues</h4>
|
||||||
<button onClick={handleClose}>
|
<button onClick={handleClose}>
|
||||||
<XMarkIcon className="h-4 w-4" />
|
<XMarkIcon className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 pb-3 px-5 border-b border-gray-200">
|
||||||
<MagnifyingGlassIcon className="h-4 w-4 text-gray-500" />
|
<MagnifyingGlassIcon className="h-4 w-4 text-gray-500" />
|
||||||
<input
|
<input
|
||||||
className="outline-none"
|
className="outline-none"
|
||||||
@ -117,7 +114,7 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
value={query}
|
value={query}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-start w-full gap-2">
|
<div className="flex flex-col items-start w-full gap-2 px-5">
|
||||||
{filteredOptions ? (
|
{filteredOptions ? (
|
||||||
filteredOptions.length > 0 ? (
|
filteredOptions.length > 0 ? (
|
||||||
filteredOptions.map((option: ICycle) => (
|
filteredOptions.map((option: ICycle) => (
|
||||||
@ -136,7 +133,13 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center text-gray-500">No matching results</p>
|
<div className="flex items-center justify-center gap-4 p-5 text-sm w-full">
|
||||||
|
<ExclamationIcon height={14} width={14} />
|
||||||
|
<span className="text-center text-gray-500">
|
||||||
|
You don’t have any current cycle. Please create one to transfer the
|
||||||
|
issues.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center text-gray-500">Loading...</p>
|
<p className="text-center text-gray-500">Loading...</p>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export const getPriorityIcon = (priority: string | null, className?: string) => {
|
export const getPriorityIcon = (priority: string | null, className?: string) => {
|
||||||
if (!className || className === "") className = "text-xs";
|
if (!className || className === "") className = "text-xs flex items-center";
|
||||||
|
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case "urgent":
|
case "urgent":
|
||||||
|
@ -145,9 +145,9 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
if (!issueLabels) return;
|
if (!issueLabels) return;
|
||||||
const label = issueLabels.find((label) => label.id === labelId);
|
const label = issueLabels.find((label) => label.id === labelId);
|
||||||
if (typeof label !== "undefined") {
|
if (typeof label !== "undefined") {
|
||||||
return label.color;
|
return label.color !== "" ? label.color : "#000000";
|
||||||
}
|
}
|
||||||
return "#64748b";
|
return "#000000";
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!issueActivities) {
|
if (!issueActivities) {
|
||||||
@ -171,7 +171,7 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flow-root">
|
<div className="flow-root">
|
||||||
<ul role="list" className="-mb-8">
|
<ul role="list" className="-mb-4">
|
||||||
{issueActivities.map((activityItem, activityItemIdx) => {
|
{issueActivities.map((activityItem, activityItemIdx) => {
|
||||||
// determines what type of action is performed
|
// determines what type of action is performed
|
||||||
let action = activityDetails[activityItem.field as keyof typeof activityDetails]?.message;
|
let action = activityDetails[activityItem.field as keyof typeof activityDetails]?.message;
|
||||||
@ -192,6 +192,18 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
activityItem.new_value && activityItem.new_value !== ""
|
activityItem.new_value && activityItem.new_value !== ""
|
||||||
? "set the due date to"
|
? "set the due date to"
|
||||||
: "removed the due date";
|
: "removed the due date";
|
||||||
|
} else if (activityItem.field === "parent") {
|
||||||
|
action =
|
||||||
|
activityItem.new_value && activityItem.new_value !== ""
|
||||||
|
? "set the parent to"
|
||||||
|
: "removed the parent";
|
||||||
|
} else if (activityItem.field === "priority") {
|
||||||
|
action =
|
||||||
|
activityItem.new_value && activityItem.new_value !== ""
|
||||||
|
? "set the priority to"
|
||||||
|
: "removed the priority";
|
||||||
|
} else if (activityItem.field === "description") {
|
||||||
|
action = "updated the";
|
||||||
}
|
}
|
||||||
// for values that are after the action clause
|
// for values that are after the action clause
|
||||||
let value: any = activityItem.new_value ? activityItem.new_value : activityItem.old_value;
|
let value: any = activityItem.new_value ? activityItem.new_value : activityItem.old_value;
|
||||||
@ -204,8 +216,8 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
} else if (activityItem.field === "state") {
|
} else if (activityItem.field === "state") {
|
||||||
value = activityItem.new_value ? addSpaceIfCamelCase(activityItem.new_value) : "None";
|
value = activityItem.new_value ? addSpaceIfCamelCase(activityItem.new_value) : "None";
|
||||||
} else if (activityItem.field === "labels") {
|
} else if (activityItem.field === "labels") {
|
||||||
let name,
|
let name;
|
||||||
id = "#64748b";
|
let id = "#000000";
|
||||||
if (activityItem.new_value !== "") {
|
if (activityItem.new_value !== "") {
|
||||||
name = activityItem.new_value;
|
name = activityItem.new_value;
|
||||||
id = activityItem.new_identifier ? activityItem.new_identifier : id;
|
id = activityItem.new_identifier ? activityItem.new_identifier : id;
|
||||||
@ -231,9 +243,13 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
} else if (activityItem.field === "assignees") {
|
} else if (activityItem.field === "assignees") {
|
||||||
value = activityItem.new_value;
|
value = activityItem.new_value;
|
||||||
} else if (activityItem.field === "target_date") {
|
} else if (activityItem.field === "target_date") {
|
||||||
value = renderShortNumericDateFormat(activityItem.new_value as string);
|
const date =
|
||||||
|
activityItem.new_value && activityItem.new_value !== ""
|
||||||
|
? activityItem.new_value
|
||||||
|
: activityItem.old_value;
|
||||||
|
value = renderShortNumericDateFormat(date as string);
|
||||||
} else if (activityItem.field === "description") {
|
} else if (activityItem.field === "description") {
|
||||||
value = "";
|
value = "description";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||||
|
@ -94,6 +94,7 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
reset,
|
reset,
|
||||||
watch,
|
watch,
|
||||||
control,
|
control,
|
||||||
|
getValues,
|
||||||
setValue,
|
setValue,
|
||||||
setFocus,
|
setFocus,
|
||||||
} = useForm<IIssue>({
|
} = useForm<IIssue>({
|
||||||
@ -272,7 +273,11 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
/>
|
/>
|
||||||
<GptAssistantModal
|
<GptAssistantModal
|
||||||
isOpen={gptAssistantModal}
|
isOpen={gptAssistantModal}
|
||||||
handleClose={() => setGptAssistantModal(false)}
|
handleClose={() => {
|
||||||
|
setGptAssistantModal(false);
|
||||||
|
// this is done so that the title do not reset after gpt popover closed
|
||||||
|
reset(getValues());
|
||||||
|
}}
|
||||||
inset="top-2 left-0"
|
inset="top-2 left-0"
|
||||||
content=""
|
content=""
|
||||||
htmlContent={watch("description_html")}
|
htmlContent={watch("description_html")}
|
||||||
|
@ -9,6 +9,8 @@ import issuesService from "services/issues.service";
|
|||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
// icons
|
// icons
|
||||||
import { CyclesIcon } from "components/icons";
|
import { CyclesIcon } from "components/icons";
|
||||||
// types
|
// types
|
||||||
@ -65,13 +67,15 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
|||||||
<div className="space-y-1 sm:basis-1/2">
|
<div className="space-y-1 sm:basis-1/2">
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label={
|
label={
|
||||||
|
<Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : ""}`}>
|
||||||
<span
|
<span
|
||||||
className={`w-full max-w-[125px] truncate text-left sm:block ${
|
className={`w-full max-w-[125px] truncate text-left sm:block ${
|
||||||
issueCycle ? "" : "text-gray-900"
|
issueCycle ? "" : "text-gray-900"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{issueCycle ? issueCycle.cycle_detail.name : "None"}
|
{issueCycle ? truncateText(issueCycle.cycle_detail.name, 15) : "None"}
|
||||||
</span>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
value={issueCycle?.cycle_detail.id}
|
value={issueCycle?.cycle_detail.id}
|
||||||
onChange={(value: any) => {
|
onChange={(value: any) => {
|
||||||
@ -81,6 +85,7 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
width="w-full"
|
width="w-full"
|
||||||
position="right"
|
position="right"
|
||||||
|
maxHeight="rg"
|
||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
>
|
>
|
||||||
{incompleteCycles ? (
|
{incompleteCycles ? (
|
||||||
@ -89,7 +94,7 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
|||||||
{incompleteCycles.map((option) => (
|
{incompleteCycles.map((option) => (
|
||||||
<CustomSelect.Option key={option.id} value={option.id}>
|
<CustomSelect.Option key={option.id} value={option.id}>
|
||||||
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
||||||
<span className="w-full max-w-[125px] truncate ">{option.name}</span>
|
<span className="w-full truncate ">{truncateText(option.name, 15)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</CustomSelect.Option>
|
</CustomSelect.Option>
|
||||||
))}
|
))}
|
||||||
|
@ -8,6 +8,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
// icons
|
// icons
|
||||||
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
|
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
@ -64,13 +66,15 @@ export const SidebarModuleSelect: React.FC<Props> = ({
|
|||||||
<div className="space-y-1 sm:basis-1/2">
|
<div className="space-y-1 sm:basis-1/2">
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label={
|
label={
|
||||||
|
<Tooltip position="left" tooltipContent={`${modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"}`}>
|
||||||
<span
|
<span
|
||||||
className={`w-full max-w-[125px] truncate text-left sm:block ${
|
className={`w-full max-w-[125px] truncate text-left sm:block ${
|
||||||
issueModule ? "" : "text-gray-900"
|
issueModule ? "" : "text-gray-900"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"}
|
{truncateText(`${modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"}`, 15)}
|
||||||
</span>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
value={issueModule?.module_detail?.id}
|
value={issueModule?.module_detail?.id}
|
||||||
onChange={(value: any) => {
|
onChange={(value: any) => {
|
||||||
@ -80,6 +84,7 @@ export const SidebarModuleSelect: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
width="w-full"
|
width="w-full"
|
||||||
position="right"
|
position="right"
|
||||||
|
maxHeight="rg"
|
||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
>
|
>
|
||||||
{modules ? (
|
{modules ? (
|
||||||
@ -88,7 +93,7 @@ export const SidebarModuleSelect: React.FC<Props> = ({
|
|||||||
{modules.map((option) => (
|
{modules.map((option) => (
|
||||||
<CustomSelect.Option key={option.id} value={option.id}>
|
<CustomSelect.Option key={option.id} value={option.id}>
|
||||||
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
||||||
<span className="w-full max-w-[125px] truncate">{option.name}</span>
|
<span className="w-full max-w-[125px] truncate">{truncateText(option.name, 15)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</CustomSelect.Option>
|
</CustomSelect.Option>
|
||||||
))}
|
))}
|
||||||
|
@ -598,7 +598,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="py-1 text-xs">
|
<div className="min-h-[116px] py-1 text-xs">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<h4>Links</h4>
|
<h4>Links</h4>
|
||||||
{!isNotAllowed && (
|
{!isNotAllowed && (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { forwardRef, useEffect } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
@ -31,12 +31,12 @@ const defaultValues: Partial<IIssueLabels> = {
|
|||||||
color: "#ff0000",
|
color: "#ff0000",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateUpdateLabelInline: React.FC<Props> = ({
|
type Ref = HTMLDivElement;
|
||||||
labelForm,
|
|
||||||
setLabelForm,
|
export const CreateUpdateLabelInline = forwardRef<Ref, Props>(function CreateUpdateLabelInline(
|
||||||
isUpdating,
|
{ labelForm, setLabelForm, isUpdating, labelToUpdate },
|
||||||
labelToUpdate,
|
ref
|
||||||
}) => {
|
) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -109,9 +109,10 @@ export const CreateUpdateLabelInline: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 rounded-[10px] border bg-white p-5 ${
|
className={`flex items-center gap-2 scroll-m-8 rounded-[10px] border bg-white p-5 ${
|
||||||
labelForm ? "" : "hidden"
|
labelForm ? "" : "hidden"
|
||||||
}`}
|
}`}
|
||||||
|
ref={ref}
|
||||||
>
|
>
|
||||||
<div className="h-8 w-8 flex-shrink-0">
|
<div className="h-8 w-8 flex-shrink-0">
|
||||||
<Popover className="relative z-10 flex h-full w-full items-center justify-center rounded-xl bg-gray-200">
|
<Popover className="relative z-10 flex h-full w-full items-center justify-center rounded-xl bg-gray-200">
|
||||||
@ -187,4 +188,4 @@ export const CreateUpdateLabelInline: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -146,7 +146,7 @@ export const LabelsListModal: React.FC<Props> = ({ isOpen, handleClose, parent }
|
|||||||
<span
|
<span
|
||||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: label.color,
|
backgroundColor: label.color !== "" ? label.color : "#000000",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{label.name}
|
{label.name}
|
||||||
|
@ -11,7 +11,14 @@ import issuesService from "services/issues.service";
|
|||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "components/ui";
|
import { CustomMenu } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { ChevronDownIcon, RectangleGroupIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
RectangleGroupIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
PlusIcon,
|
||||||
|
PencilIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IIssueLabels } from "types";
|
import { IIssueLabels } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -72,11 +79,22 @@ export const SingleLabelGroup: React.FC<Props> = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CustomMenu ellipsis>
|
<CustomMenu ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={() => addLabelToGroup(label)}>
|
<CustomMenu.MenuItem onClick={() => addLabelToGroup(label)}>
|
||||||
Add more labels
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
<span>Add more labels</span>
|
||||||
|
</span>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem onClick={() => editLabel(label)}>
|
||||||
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<PencilIcon className="h-4 w-4" />
|
||||||
|
<span>Edit label</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={() => editLabel(label)}>Edit</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem onClick={() => handleLabelDelete(label.id)}>
|
<CustomMenu.MenuItem onClick={() => handleLabelDelete(label.id)}>
|
||||||
Delete
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
<span>Delete label</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
<Disclosure.Button>
|
<Disclosure.Button>
|
||||||
@ -117,13 +135,22 @@ export const SingleLabelGroup: React.FC<Props> = ({
|
|||||||
<div className="pointer-events-none opacity-0 group-hover:pointer-events-auto group-hover:opacity-100">
|
<div className="pointer-events-none opacity-0 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||||
<CustomMenu ellipsis>
|
<CustomMenu ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={() => removeFromGroup(child)}>
|
<CustomMenu.MenuItem onClick={() => removeFromGroup(child)}>
|
||||||
Remove from group
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<XMarkIcon className="h-4 w-4" />
|
||||||
|
<span>Remove from group</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={() => editLabel(child)}>
|
<CustomMenu.MenuItem onClick={() => editLabel(child)}>
|
||||||
Edit
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<PencilIcon className="h-4 w-4" />
|
||||||
|
<span>Edit label</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={() => handleLabelDelete(child.id)}>
|
<CustomMenu.MenuItem onClick={() => handleLabelDelete(child.id)}>
|
||||||
Delete
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
<span>Delete label</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,8 @@ import React from "react";
|
|||||||
import { CustomMenu } from "components/ui";
|
import { CustomMenu } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { IIssueLabels } from "types";
|
import { IIssueLabels } from "types";
|
||||||
|
//icons
|
||||||
|
import { RectangleGroupIcon, LinkIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: IIssueLabels;
|
label: IIssueLabels;
|
||||||
@ -31,11 +33,22 @@ export const SingleLabel: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<CustomMenu ellipsis>
|
<CustomMenu ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={() => addLabelToGroup(label)}>
|
<CustomMenu.MenuItem onClick={() => addLabelToGroup(label)}>
|
||||||
Convert to group
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<RectangleGroupIcon className="h-4 w-4" />
|
||||||
|
<span>Convert to group</span>
|
||||||
|
</span>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem onClick={() => editLabel(label)}>
|
||||||
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<PencilIcon className="h-4 w-4" />
|
||||||
|
<span>Edit label</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={() => editLabel(label)}>Edit</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem onClick={() => handleLabelDelete(label.id)}>
|
<CustomMenu.MenuItem onClick={() => handleLabelDelete(label.id)}>
|
||||||
Delete
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
<span>Delete label</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +25,11 @@ type Props = {
|
|||||||
export const Workspace: React.FC<Props> = ({ setStep, setWorkspace }) => {
|
export const Workspace: React.FC<Props> = ({ setStep, setWorkspace }) => {
|
||||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||||
|
const [defaultValues, setDefaultValues] = useState({
|
||||||
|
name: "",
|
||||||
|
slug: "",
|
||||||
|
company_size: null,
|
||||||
|
});
|
||||||
|
|
||||||
const { data: invitations, mutate } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
|
const { data: invitations, mutate } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
|
||||||
workspaceService.userWorkspaceInvitations()
|
workspaceService.userWorkspaceInvitations()
|
||||||
@ -99,11 +104,13 @@ export const Workspace: React.FC<Props> = ({ setStep, setWorkspace }) => {
|
|||||||
setWorkspace(res);
|
setWorkspace(res);
|
||||||
setStep(3);
|
setStep(3);
|
||||||
}}
|
}}
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
setDefaultValues={setDefaultValues}
|
||||||
/>
|
/>
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<div className="divide-y pb-8 px-4">
|
<div className="divide-y px-4 pb-8">
|
||||||
{invitations && invitations.length > 0 ? (
|
{invitations && invitations.length > 0 ? (
|
||||||
invitations.map((invitation) => (
|
invitations.map((invitation) => (
|
||||||
<div key={invitation.id}>
|
<div key={invitation.id}>
|
||||||
|
@ -115,7 +115,7 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
|
||||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
@ -130,9 +130,9 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
|
|||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to delete Page - {`"`}
|
Are you sure you want to delete Page- {" "}
|
||||||
<span className="italic">{data?.name}</span>
|
<span className="font-bold">{data?.name}</span>
|
||||||
{`"`} ? All of the data related to the page will be permanently removed.
|
? All of the data related to the page will be permanently removed.
|
||||||
This action cannot be undone.
|
This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -291,9 +291,9 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
|
|||||||
<CustomMenu.MenuItem onClick={pushBlockIntoIssues}>
|
<CustomMenu.MenuItem onClick={pushBlockIntoIssues}>
|
||||||
Push into issues
|
Push into issues
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={editAndPushBlockIntoIssues}>
|
{/* <CustomMenu.MenuItem onClick={editAndPushBlockIntoIssues}>
|
||||||
Edit and push into issues
|
Edit and push into issues
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem> */}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<CustomMenu.MenuItem onClick={deletePageBlock}>Delete block</CustomMenu.MenuItem>
|
<CustomMenu.MenuItem onClick={deletePageBlock}>Delete block</CustomMenu.MenuItem>
|
||||||
|
@ -89,13 +89,13 @@ export const SinglePageDetailedItem: React.FC<TSingleStatProps> = ({
|
|||||||
)}
|
)}
|
||||||
<CustomMenu verticalEllipsis>
|
<CustomMenu verticalEllipsis>
|
||||||
<CustomMenu.MenuItem onClick={handleEditPage}>
|
<CustomMenu.MenuItem onClick={handleEditPage}>
|
||||||
<span className="flex items-center justify-start gap-2 text-gray-800">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<PencilIcon className="h-3.5 w-3.5" />
|
<PencilIcon className="h-3.5 w-3.5" />
|
||||||
<span>Edit Page</span>
|
<span>Edit Page</span>
|
||||||
</span>
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={handleDeletePage}>
|
<CustomMenu.MenuItem onClick={handleDeletePage}>
|
||||||
<span className="flex items-center justify-start gap-2 text-gray-800">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<TrashIcon className="h-3.5 w-3.5" />
|
<TrashIcon className="h-3.5 w-3.5" />
|
||||||
<span>Delete Page</span>
|
<span>Delete Page</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -102,7 +102,7 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
|
|||||||
handleEditPage();
|
handleEditPage();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex items-center justify-start gap-2 text-gray-800">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<PencilIcon className="h-3.5 w-3.5" />
|
<PencilIcon className="h-3.5 w-3.5" />
|
||||||
<span>Edit Page</span>
|
<span>Edit Page</span>
|
||||||
</span>
|
</span>
|
||||||
@ -114,7 +114,7 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
|
|||||||
handleDeletePage();
|
handleDeletePage();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex items-center justify-start gap-2 text-gray-800">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<TrashIcon className="h-3.5 w-3.5" />
|
<TrashIcon className="h-3.5 w-3.5" />
|
||||||
<span>Delete Page</span>
|
<span>Delete Page</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -53,7 +53,7 @@ const ConfirmProjectMemberRemove: React.FC<Props> = ({ isOpen, onClose, data, ha
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
|
||||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
@ -68,9 +68,9 @@ const ConfirmProjectMemberRemove: React.FC<Props> = ({ isOpen, onClose, data, ha
|
|||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to remove member - {`"`}
|
Are you sure you want to remove member- {" "}
|
||||||
<span className="italic">{data?.email}</span>
|
<span className="font-bold">{data?.email}</span>
|
||||||
{`"`} ? They will no longer have access to this project. This action
|
? They will no longer have access to this project. This action
|
||||||
cannot be undone.
|
cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,7 +104,7 @@ export const DeleteStateModal: React.FC<Props> = ({ isOpen, onClose, data }) =>
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
|
||||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
@ -119,9 +119,9 @@ export const DeleteStateModal: React.FC<Props> = ({ isOpen, onClose, data }) =>
|
|||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to delete state - {`"`}
|
Are you sure you want to delete state- {" "}
|
||||||
<span className="italic">{data?.name}</span>
|
<span className="italic">{data?.name}</span>
|
||||||
{`"`} ? All of the data related to the state will be permanently removed.
|
? All of the data related to the state will be permanently removed.
|
||||||
This action cannot be undone.
|
This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,7 +96,7 @@ export const DeleteViewModal: React.FC<Props> = ({ isOpen, data, onClose, onSucc
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
|
||||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
@ -111,9 +111,9 @@ export const DeleteViewModal: React.FC<Props> = ({ isOpen, data, onClose, onSucc
|
|||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to delete view - {`"`}
|
Are you sure you want to delete view- {" "}
|
||||||
<span className="italic">{data?.name}</span>
|
<span className="font-bold">{data?.name}</span>
|
||||||
{`?"`} All of the data related to the view will be permanently removed.
|
? All of the data related to the view will be permanently removed.
|
||||||
This action cannot be undone.
|
This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,16 +20,11 @@ import { VIEWS_LIST } from "constants/fetch-keys";
|
|||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
view: IView,
|
view: IView;
|
||||||
setSelectedView: React.Dispatch<React.SetStateAction<IView | null>>,
|
setSelectedView: React.Dispatch<React.SetStateAction<IView | null>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SingleViewItem: React.FC<Props> = ({ view, setSelectedView }) => {
|
||||||
export const SingleViewItem: React.FC<Props> = ({
|
|
||||||
view,
|
|
||||||
setSelectedView,
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -86,36 +81,48 @@ export const SingleViewItem: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className="flex items-center justify-between border-b bg-white p-4 first:rounded-t-[10px] last:rounded-b-[10px]"
|
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
|
||||||
>
|
<div className="flex items-center cursor-pointer justify-between border-b bg-white p-4 first:rounded-t-[10px] last:rounded-b-[10px]">
|
||||||
<div className="flex flex-col w-full gap-3">
|
<div className="flex flex-col w-full gap-3">
|
||||||
<div className="flex justify-between w-full">
|
<div className="flex justify-between w-full">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StackedLayersIcon height={18} width={18} />
|
<StackedLayersIcon height={18} width={18} />
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
|
|
||||||
<a>{view.name}</a>
|
<a>{view.name}</a>
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{
|
{view.is_favorite ? (
|
||||||
view.is_favorite ? (
|
<button
|
||||||
<button type="button" onClick={handleRemoveFromFavorites}>
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleRemoveFromFavorites();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button type="button" onClick={handleAddToFavorites}>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleAddToFavorites();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StarIcon className="h-4 w-4 " color="#858E96" />
|
<StarIcon className="h-4 w-4 " color="#858E96" />
|
||||||
</button>
|
</button>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
<CustomMenu width="auto" verticalEllipsis>
|
<CustomMenu width="auto" verticalEllipsis>
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
setSelectedView(view);
|
setSelectedView(view);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex items-center justify-start gap-2 text-gray-800">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<TrashIcon className="h-4 w-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
</span>
|
</span>
|
||||||
@ -123,10 +130,14 @@ export const SingleViewItem: React.FC<Props> = ({
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{view?.description && <p className="text-sm text-[#858E96] font-normal leading-5 px-[27px]">
|
{view?.description && (
|
||||||
|
<p className="text-sm text-[#858E96] font-normal leading-5 px-[27px]">
|
||||||
{view.description}
|
{view.description}
|
||||||
</p>}
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
</Link>
|
||||||
}
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -31,7 +31,7 @@ export const CompletedIssuesGraph: React.FC<Props> = ({ month, issues, setMonth
|
|||||||
|
|
||||||
for (let i = 1; i <= weeks; i++) {
|
for (let i = 1; i <= weeks; i++) {
|
||||||
data.push({
|
data.push({
|
||||||
week_in_month: i,
|
week_in_month: `Week ${i}`,
|
||||||
completed_count: issues?.find((item) => item.week_in_month === i)?.completed_count ?? 0,
|
completed_count: issues?.find((item) => item.week_in_month === i)?.completed_count ?? 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -58,8 +58,8 @@ export const CompletedIssuesGraph: React.FC<Props> = ({ month, issues, setMonth
|
|||||||
<div className="rounded-[10px] border bg-white p-8 pl-4">
|
<div className="rounded-[10px] border bg-white p-8 pl-4">
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
<LineChart data={data}>
|
<LineChart data={data}>
|
||||||
<CartesianGrid stroke="#e2e2e2" />
|
<CartesianGrid stroke="#e2e2e280" />
|
||||||
<XAxis dataKey="week_in_month" />
|
<XAxis dataKey="week_in_month" padding={{ left: 48, right: 48 }} />
|
||||||
<YAxis dataKey="completed_count" allowDecimals={false} />
|
<YAxis dataKey="completed_count" allowDecimals={false} />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip />} />
|
||||||
<Line
|
<Line
|
||||||
|
@ -53,7 +53,7 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
|
||||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
@ -68,9 +68,9 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
|
|||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to remove member - {`"`}
|
Are you sure you want to remove member- {" "}
|
||||||
<span className="italic">{data?.email}</span>
|
<span className="font-bold">{data?.email}</span>
|
||||||
{`"`} ? They will no longer have access to this workspace. This action
|
? They will no longer have access to this workspace. This action
|
||||||
cannot be undone.
|
cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
@ -19,15 +19,19 @@ import { COMPANY_SIZE } from "constants/workspace";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSubmit: (res: IWorkspace) => void;
|
onSubmit: (res: IWorkspace) => void;
|
||||||
|
defaultValues: {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
company_size: number | null;
|
||||||
|
};
|
||||||
|
setDefaultValues: Dispatch<SetStateAction<any>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues = {
|
export const CreateWorkspaceForm: React.FC<Props> = ({
|
||||||
name: "",
|
onSubmit,
|
||||||
slug: "",
|
defaultValues,
|
||||||
company_size: null,
|
setDefaultValues,
|
||||||
};
|
}) => {
|
||||||
|
|
||||||
export const CreateWorkspaceForm: React.FC<Props> = ({ onSubmit }) => {
|
|
||||||
const [slugError, setSlugError] = useState(false);
|
const [slugError, setSlugError] = useState(false);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -37,7 +41,7 @@ export const CreateWorkspaceForm: React.FC<Props> = ({ onSubmit }) => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
getValues,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<IWorkspace>({ defaultValues });
|
} = useForm<IWorkspace>({ defaultValues });
|
||||||
|
|
||||||
@ -72,9 +76,13 @@ export const CreateWorkspaceForm: React.FC<Props> = ({ onSubmit }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(
|
||||||
reset(defaultValues);
|
() => () => {
|
||||||
}, [reset]);
|
// when the component unmounts set the default values to whatever user typed in
|
||||||
|
setDefaultValues(getValues());
|
||||||
|
},
|
||||||
|
[getValues, setDefaultValues]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
|
@ -89,19 +89,20 @@ export const timeAgo = (time: any) => {
|
|||||||
return time;
|
return time;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDateRangeStatus = (startDate: string | null, endDate: string | null) => {
|
export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => {
|
||||||
if (!startDate || !endDate) return "draft";
|
if (!startDate || !endDate) return "draft";
|
||||||
|
|
||||||
const now = new Date();
|
const today = renderDateFormat(new Date());
|
||||||
|
const now = new Date(today);
|
||||||
const start = new Date(startDate);
|
const start = new Date(startDate);
|
||||||
const end = new Date(endDate);
|
const end = new Date(endDate);
|
||||||
|
|
||||||
if (end < now) {
|
if (start <= now && end >= now) {
|
||||||
return "completed";
|
|
||||||
} else if (start <= now && end >= now) {
|
|
||||||
return "current";
|
return "current";
|
||||||
} else {
|
} else if (start > now) {
|
||||||
return "upcoming";
|
return "upcoming";
|
||||||
|
} else {
|
||||||
|
return "completed";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -170,4 +171,5 @@ export const renderShortTime = (date: string | Date) => {
|
|||||||
return hours + ":" + minutes;
|
return hours + ":" + minutes;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDateRangeValid = (startDate: string, endDate: string)=> new Date(startDate) < new Date(endDate);
|
export const isDateRangeValid = (startDate: string, endDate: string) =>
|
||||||
|
new Date(startDate) < new Date(endDate);
|
||||||
|
@ -118,14 +118,14 @@ const useIssuesView = () => {
|
|||||||
[key: string]: IIssue[];
|
[key: string]: IIssue[];
|
||||||
}
|
}
|
||||||
| undefined = useMemo(() => {
|
| undefined = useMemo(() => {
|
||||||
const issuesToGroup = cycleIssues ?? moduleIssues ?? projectIssues;
|
const issuesToGroup = cycleId ? cycleIssues : moduleId ? moduleIssues : projectIssues;
|
||||||
|
|
||||||
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
|
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
|
||||||
if (groupByProperty === "state")
|
if (groupByProperty === "state")
|
||||||
return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined;
|
return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined;
|
||||||
|
|
||||||
return issuesToGroup;
|
return issuesToGroup;
|
||||||
}, [projectIssues, cycleIssues, moduleIssues, groupByProperty]);
|
}, [projectIssues, cycleIssues, moduleIssues, groupByProperty, cycleId, moduleId]);
|
||||||
|
|
||||||
const isEmpty =
|
const isEmpty =
|
||||||
Object.values(groupedByIssues ?? {}).every((group) => group.length === 0) ||
|
Object.values(groupedByIssues ?? {}).every((group) => group.length === 0) ||
|
||||||
|
@ -11,8 +11,7 @@ import AppLayout from "layouts/app-layout";
|
|||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// helpers
|
import { Feeds } from "components/core";
|
||||||
import { timeAgo } from "helpers/date-time.helper";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_ACTIVITY } from "constants/fetch-keys";
|
import { USER_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -33,21 +32,9 @@ const ProfileActivity = () => {
|
|||||||
profilePage
|
profilePage
|
||||||
>
|
>
|
||||||
{userActivity ? (
|
{userActivity ? (
|
||||||
<div className="divide-y rounded-[10px] border border-gray-200 bg-white px-6 -mt-4">
|
userActivity.results.length > 0 ? (
|
||||||
{userActivity.results.length > 0
|
<Feeds activities={userActivity.results} />
|
||||||
? userActivity.results.map((activity) => (
|
) : null
|
||||||
<div
|
|
||||||
key={activity.id}
|
|
||||||
className="flex items-center gap-2 justify-between py-4 text-sm text-gray-500"
|
|
||||||
>
|
|
||||||
<h4>
|
|
||||||
<span className="font-medium text-black">{activity.comment}</span>
|
|
||||||
</h4>
|
|
||||||
<div className="text-xs">{timeAgo(activity.created_at)}</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<Loader className="space-y-5">
|
<Loader className="space-y-5">
|
||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
|
@ -12,7 +12,6 @@ import { requiredAdmin } from "lib/auth";
|
|||||||
import AppLayout from "layouts/app-layout";
|
import AppLayout from "layouts/app-layout";
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import workspaceService from "services/workspace.service";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -22,7 +21,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
import { IProject, IWorkspace } from "types";
|
import { IProject, IWorkspace } from "types";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage, GetServerSidePropsContext } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type TControlSettingsProps = {
|
type TControlSettingsProps = {
|
||||||
isMember: boolean;
|
isMember: boolean;
|
||||||
@ -53,8 +52,10 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: people } = useSWR(
|
const { data: people } = useSWR(
|
||||||
workspaceSlug ? WORKSPACE_MEMBERS : null,
|
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||||
workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null
|
workspaceSlug && projectId
|
||||||
|
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||||
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -101,7 +102,6 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
||||||
@ -140,7 +140,7 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
>
|
>
|
||||||
{people?.map((person) => (
|
{people?.map((person) => (
|
||||||
<CustomSelect.Option
|
<CustomSelect.Option
|
||||||
key={person.id}
|
key={person.member.id}
|
||||||
value={person.member.id}
|
value={person.member.id}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
@ -200,7 +200,7 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
>
|
>
|
||||||
{people?.map((person) => (
|
{people?.map((person) => (
|
||||||
<CustomSelect.Option
|
<CustomSelect.Option
|
||||||
key={person.id}
|
key={person.member.id}
|
||||||
value={person.member.id}
|
value={person.member.id}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
|
@ -68,16 +68,6 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkIdentifier = (value: string) => {
|
|
||||||
if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
projectService
|
|
||||||
.checkProjectIdentifierAvailability(workspaceSlug as string, value)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.exists) setError("identifier", { message: "Identifier already exists" });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectDetails)
|
if (projectDetails)
|
||||||
reset({
|
reset({
|
||||||
@ -88,24 +78,9 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
});
|
});
|
||||||
}, [projectDetails, reset]);
|
}, [projectDetails, reset]);
|
||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const updateProject = async (payload: Partial<IProject>) => {
|
||||||
if (!workspaceSlug || !projectDetails) return;
|
if (!workspaceSlug || !projectDetails) return;
|
||||||
|
|
||||||
const payload: Partial<IProject> = {
|
|
||||||
name: formData.name,
|
|
||||||
network: formData.network,
|
|
||||||
identifier: formData.identifier,
|
|
||||||
description: formData.description,
|
|
||||||
default_assignee: formData.default_assignee,
|
|
||||||
project_lead: formData.project_lead,
|
|
||||||
icon: formData.icon,
|
|
||||||
};
|
|
||||||
|
|
||||||
await projectService
|
|
||||||
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
|
||||||
.then(async (res) => {
|
|
||||||
if (res.exists) setError("identifier", { message: "Identifier already exists" });
|
|
||||||
else
|
|
||||||
await projectService
|
await projectService
|
||||||
.updateProject(workspaceSlug as string, projectDetails.id, payload)
|
.updateProject(workspaceSlug as string, projectDetails.id, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -128,7 +103,29 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
message: "Project could not be updated. Please try again.",
|
message: "Project could not be updated. Please try again.",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formData: IProject) => {
|
||||||
|
if (!workspaceSlug || !projectDetails) return;
|
||||||
|
|
||||||
|
const payload: Partial<IProject> = {
|
||||||
|
name: formData.name,
|
||||||
|
network: formData.network,
|
||||||
|
identifier: formData.identifier,
|
||||||
|
description: formData.description,
|
||||||
|
default_assignee: formData.default_assignee,
|
||||||
|
project_lead: formData.project_lead,
|
||||||
|
icon: formData.icon,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (projectDetails.identifier !== formData.identifier)
|
||||||
|
await projectService
|
||||||
|
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
||||||
|
.then(async (res) => {
|
||||||
|
if (res.exists) setError("identifier", { message: "Identifier already exists" });
|
||||||
|
else await updateProject(payload);
|
||||||
});
|
});
|
||||||
|
else await updateProject(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useRef } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
@ -46,6 +46,8 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const scollToRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR(
|
const { data: projectDetails } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
@ -128,6 +130,7 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
setLabelForm={setLabelForm}
|
setLabelForm={setLabelForm}
|
||||||
isUpdating={isUpdating}
|
isUpdating={isUpdating}
|
||||||
labelToUpdate={labelToUpdate}
|
labelToUpdate={labelToUpdate}
|
||||||
|
ref={scollToRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<>
|
<>
|
||||||
@ -142,7 +145,12 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
key={label.id}
|
key={label.id}
|
||||||
label={label}
|
label={label}
|
||||||
addLabelToGroup={() => addLabelToGroup(label)}
|
addLabelToGroup={() => addLabelToGroup(label)}
|
||||||
editLabel={editLabel}
|
editLabel={(label) => {
|
||||||
|
editLabel(label);
|
||||||
|
scollToRef.current?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}}
|
||||||
handleLabelDelete={handleLabelDelete}
|
handleLabelDelete={handleLabelDelete}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -153,7 +161,12 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
label={label}
|
label={label}
|
||||||
labelChildren={children}
|
labelChildren={children}
|
||||||
addLabelToGroup={addLabelToGroup}
|
addLabelToGroup={addLabelToGroup}
|
||||||
editLabel={editLabel}
|
editLabel={(label) => {
|
||||||
|
editLabel(label);
|
||||||
|
scollToRef.current?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}}
|
||||||
handleLabelDelete={handleLabelDelete}
|
handleLabelDelete={handleLabelDelete}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,7 @@ import SendProjectInvitationModal from "components/project/send-project-invitati
|
|||||||
import { CustomMenu, CustomSelect, Loader } from "components/ui";
|
import { CustomMenu, CustomSelect, Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage, GetServerSidePropsContext } from "next";
|
||||||
import { UserAuth } from "types";
|
import { UserAuth } from "types";
|
||||||
@ -261,7 +261,10 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
else setSelectedInviteRemoveMember(member.id);
|
else setSelectedInviteRemoveMember(member.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Remove member
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<XMarkIcon className="h-4 w-4" />
|
||||||
|
<span>Remove member</span>
|
||||||
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,9 @@ import AppLayout from "layouts/app-layout";
|
|||||||
// ui
|
// ui
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
|
|
||||||
|
//icons
|
||||||
|
import { PlusIcon } from "components/icons";
|
||||||
|
|
||||||
// image
|
// image
|
||||||
import emptyView from "public/empty-state/empty-view.svg";
|
import emptyView from "public/empty-state/empty-view.svg";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
@ -64,7 +67,8 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
|
|||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<PrimaryButton type="button" onClick={() => setIsCreateViewModalOpen(true)}>
|
<PrimaryButton type="button" className="flex items-center gap-2" onClick={() => setIsCreateViewModalOpen(true)}>
|
||||||
|
<PlusIcon className="w-4 h-4" />
|
||||||
Create View
|
Create View
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,11 @@ import { CreateWorkspaceForm } from "components/workspace";
|
|||||||
|
|
||||||
const CreateWorkspace: NextPage = () => {
|
const CreateWorkspace: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const defaultValues = {
|
||||||
|
name: "",
|
||||||
|
slug: "",
|
||||||
|
company_size: null,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
@ -24,7 +29,11 @@ const CreateWorkspace: NextPage = () => {
|
|||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<Image src={Logo} height="50" alt="Plane Logo" />
|
<Image src={Logo} height="50" alt="Plane Logo" />
|
||||||
</div>
|
</div>
|
||||||
<CreateWorkspaceForm onSubmit={(res) => router.push(`/${res.slug}`)} />
|
<CreateWorkspaceForm
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
setDefaultValues={() => {}}
|
||||||
|
onSubmit={(res) => router.push(`/${res.slug}`)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
Loading…
Reference in New Issue
Block a user