Merge pull request #669 from makeplane/develop

promote: develop to stage-release
This commit is contained in:
Vamsi Kurama 2023-04-01 20:19:02 +05:30 committed by GitHub
commit 93ba04aebc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 820 additions and 356 deletions

View File

@ -5,6 +5,8 @@ on:
branches:
- 'develop'
- 'master'
tags:
- '*'
jobs:
build_push_frontend:

View File

@ -9,9 +9,11 @@ import issuesService from "services/issues.service";
import projectService from "services/project.service";
// hooks
import useIssuesView from "hooks/use-issues-view";
// component
import { Avatar } from "components/ui";
// icons
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, PlusIcon } from "@heroicons/react/24/outline";
import { getStateGroupIcon } from "components/icons";
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
@ -89,9 +91,39 @@ export const BoardHeader: React.FC<Props> = ({
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 (
<div
className={`flex justify-between px-1 ${
className={`flex justify-between items-center px-1 ${
!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" : ""
}`}
>
{currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor)}
<span className="flex items-center">{getGroupIcon()}</span>
<h2
className="text-lg font-semibold capitalize"
style={{

View 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>
);

View File

@ -131,11 +131,11 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100"
: priority === "low"
? "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>{priority}</span>
<span>{priority ? priority : "None"}</span>
<span
className="cursor-pointer"
onClick={() =>

View File

@ -11,3 +11,4 @@ export * from "./link-modal";
export * from "./not-authorized-view";
export * from "./image-picker-popover";
export * from "./filter-list";
export * from "./feeds";

View File

@ -29,22 +29,16 @@ import {
RectangleStackIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { ExclamationIcon, getStateGroupIcon, TransferIcon } from "components/icons";
import { ExclamationIcon, TransferIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import {
CycleIssueResponse,
IIssue,
IIssueFilterOptions,
ModuleIssueResponse,
UserAuth,
} from "types";
import { IIssue, IIssueFilterOptions, UserAuth } from "types";
// fetch-keys
import {
CYCLE_ISSUES,
CYCLE_DETAILS,
CYCLE_ISSUES_WITH_PARAMS,
MODULE_ISSUES,
MODULE_DETAILS,
MODULE_ISSUES_WITH_PARAMS,
PROJECT_ISSUES_LIST_WITH_PARAMS,
STATE_LIST,
@ -266,8 +260,14 @@ export const IssuesView: React.FC<Props> = ({
sort_order: draggedItem.sort_order,
})
.then(() => {
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
if (cycleId) {
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));
});
}
@ -322,13 +322,9 @@ export const IssuesView: React.FC<Props> = ({
const removeIssueFromCycle = useCallback(
(bridgeId: string) => {
if (!workspaceSlug || !projectId) return;
if (!workspaceSlug || !projectId || !cycleId) return;
mutate<CycleIssueResponse[]>(
CYCLE_ISSUES(cycleId as string),
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
false
);
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
issuesService
.removeIssueFromCycle(
@ -344,18 +340,14 @@ export const IssuesView: React.FC<Props> = ({
console.log(e);
});
},
[workspaceSlug, projectId, cycleId]
[workspaceSlug, projectId, cycleId, params]
);
const removeIssueFromModule = useCallback(
(bridgeId: string) => {
if (!workspaceSlug || !projectId) return;
if (!workspaceSlug || !projectId || !moduleId) return;
mutate<ModuleIssueResponse[]>(
MODULE_ISSUES(moduleId as string),
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
false
);
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
modulesService
.removeIssueFromModule(
@ -371,7 +363,7 @@ export const IssuesView: React.FC<Props> = ({
console.log(e);
});
},
[workspaceSlug, projectId, moduleId]
[workspaceSlug, projectId, moduleId, params]
);
const handleTrashBox = useCallback(

View File

@ -12,10 +12,10 @@ import useIssuesProperties from "hooks/use-issue-properties";
// components
import { SingleListIssue } from "components/core";
// ui
import { CustomMenu } from "components/ui";
import { Avatar, CustomMenu } from "components/ui";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
import { getStateGroupIcon } from "components/icons";
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
@ -99,6 +99,36 @@ export const SingleList: React.FC<Props> = ({
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 (
<Disclosure key={groupTitle} as="div" defaultOpen>
{({ open }) => (
@ -110,12 +140,8 @@ export const SingleList: React.FC<Props> = ({
>
<Disclosure.Button>
<div className="flex items-center gap-x-3">
{selectedGroup !== null && selectedGroup === "state" ? (
<span>
{currentState && getStateGroupIcon(currentState.group, "16", "16", bgColor)}
</span>
) : (
""
{selectedGroup !== null && (
<span className="flex items-center">{getGroupIcon()}</span>
)}
{selectedGroup !== null ? (
<h2 className="text-base font-semibold capitalize leading-6 text-gray-800">

View File

@ -80,6 +80,7 @@ const ProgressChart: React.FC<Props> = ({ issues, start, end }) => {
tick={{ fontSize: "12px", fill: "#1f2937" }}
tickSize={10}
minTickGap={10}
allowDecimals={false}
/>
<Tooltip content={<CustomTooltip />} />
<Area

View File

@ -77,6 +77,23 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
await cycleService
.updateCycle(workspaceSlug as string, projectId as string, cycleId, payload)
.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)) {
case "completed":
mutate(CYCLE_COMPLETE_LIST(projectId as string));
@ -90,6 +107,8 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
default:
mutate(CYCLE_DRAFT_LIST(projectId as string));
}
}
handleClose();
setToastAlert({

View File

@ -90,16 +90,13 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
const startDate = new Date(cycle.start_date ?? "");
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);
if (cycleStatus === "current" || cycleStatus === "upcoming")
switch (cycleStatus) {
case "current":
case "upcoming":
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
@ -114,7 +111,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}),
false
);
else if (cycleStatus === "completed")
break;
case "completed":
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
@ -125,7 +123,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}),
false
);
else
break;
case "draft":
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
@ -136,12 +135,12 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}),
false
);
break;
}
setToastAlert({
type: "success",
title: "Success!",
message: "Successfully added the cycle to favorites.",
});
cyclesService
.addCycleToFavorites(workspaceSlug as string, projectId as string, {
cycle: cycle.id,
})
.catch(() => {
setToastAlert({
@ -153,14 +152,13 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
};
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);
if (cycleStatus === "current" || cycleStatus === "upcoming")
switch (cycleStatus) {
case "current":
case "upcoming":
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
@ -175,7 +173,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}),
false
);
else if (cycleStatus === "completed")
break;
case "completed":
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
@ -186,7 +185,8 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}),
false
);
else
break;
case "draft":
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
@ -197,13 +197,11 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}),
false
);
break;
}
setToastAlert({
type: "success",
title: "Success!",
message: "Successfully removed the cycle from favorites.",
});
})
cyclesService
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
.catch(() => {
setToastAlert({
type: "error",

View File

@ -12,7 +12,7 @@ import cyclesService from "services/cycles.service";
import useToast from "hooks/use-toast";
//icons
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { ContrastIcon, CyclesIcon } from "components/icons";
import { ContrastIcon, CyclesIcon, ExclamationIcon } from "components/icons";
// fetch-key
import { CYCLE_INCOMPLETE_LIST } from "constants/fetch-keys";
// types
@ -31,7 +31,6 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
const { setToastAlert } = useToast();
const transferIssue = async (payload: any) => {
await cyclesService
.transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload)
@ -39,16 +38,14 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
setToastAlert({
type: "success",
title: "Issues transfered successfully",
message:
"Issues have been transferred successfully",
message: "Issues have been transferred successfully",
});
})
.catch((err) => {
setToastAlert({
type: "error",
title: "Error!",
message:
"Issues cannot be transfer. Please try again.",
message: "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"
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 items-center justify-between">
<div className="flex items-center justify-between px-5">
<h4 className="text-gray-700 text-base">Transfer Issues</h4>
<button onClick={handleClose}>
<XMarkIcon className="h-4 w-4" />
</button>
</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" />
<input
className="outline-none"
@ -117,7 +114,7 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
value={query}
/>
</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.length > 0 ? (
filteredOptions.map((option: ICycle) => (
@ -136,7 +133,13 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
</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 dont have any current cycle. Please create one to transfer the
issues.
</span>
</div>
)
) : (
<p className="text-center text-gray-500">Loading...</p>

View File

@ -1,5 +1,5 @@
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) {
case "urgent":

View File

@ -145,9 +145,9 @@ export const IssueActivitySection: React.FC<Props> = () => {
if (!issueLabels) return;
const label = issueLabels.find((label) => label.id === labelId);
if (typeof label !== "undefined") {
return label.color;
return label.color !== "" ? label.color : "#000000";
}
return "#64748b";
return "#000000";
};
if (!issueActivities) {
@ -171,7 +171,7 @@ export const IssueActivitySection: React.FC<Props> = () => {
return (
<div className="flow-root">
<ul role="list" className="-mb-8">
<ul role="list" className="-mb-4">
{issueActivities.map((activityItem, activityItemIdx) => {
// determines what type of action is performed
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 !== ""
? "set the due date to"
: "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
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") {
value = activityItem.new_value ? addSpaceIfCamelCase(activityItem.new_value) : "None";
} else if (activityItem.field === "labels") {
let name,
id = "#64748b";
let name;
let id = "#000000";
if (activityItem.new_value !== "") {
name = activityItem.new_value;
id = activityItem.new_identifier ? activityItem.new_identifier : id;
@ -231,9 +243,13 @@ export const IssueActivitySection: React.FC<Props> = () => {
} else if (activityItem.field === "assignees") {
value = activityItem.new_value;
} 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") {
value = "";
value = "description";
}
if ("field" in activityItem && activityItem.field !== "updated_by") {

View File

@ -94,6 +94,7 @@ export const IssueForm: FC<IssueFormProps> = ({
reset,
watch,
control,
getValues,
setValue,
setFocus,
} = useForm<IIssue>({
@ -272,7 +273,11 @@ export const IssueForm: FC<IssueFormProps> = ({
/>
<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"
content=""
htmlContent={watch("description_html")}

View File

@ -9,6 +9,8 @@ import issuesService from "services/issues.service";
import cyclesService from "services/cycles.service";
// ui
import { Spinner, CustomSelect, Tooltip } from "components/ui";
// helper
import { truncateText } from "helpers/string.helper";
// icons
import { CyclesIcon } from "components/icons";
// types
@ -65,13 +67,15 @@ export const SidebarCycleSelect: React.FC<Props> = ({
<div className="space-y-1 sm:basis-1/2">
<CustomSelect
label={
<Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : ""}`}>
<span
className={`w-full max-w-[125px] truncate text-left sm:block ${
issueCycle ? "" : "text-gray-900"
}`}
>
{issueCycle ? issueCycle.cycle_detail.name : "None"}
{issueCycle ? truncateText(issueCycle.cycle_detail.name, 15) : "None"}
</span>
</Tooltip>
}
value={issueCycle?.cycle_detail.id}
onChange={(value: any) => {
@ -81,6 +85,7 @@ export const SidebarCycleSelect: React.FC<Props> = ({
}}
width="w-full"
position="right"
maxHeight="rg"
disabled={isNotAllowed}
>
{incompleteCycles ? (
@ -89,7 +94,7 @@ export const SidebarCycleSelect: React.FC<Props> = ({
{incompleteCycles.map((option) => (
<CustomSelect.Option key={option.id} value={option.id}>
<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>
</CustomSelect.Option>
))}

View File

@ -8,6 +8,8 @@ import useSWR, { mutate } from "swr";
import modulesService from "services/modules.service";
// ui
import { Spinner, CustomSelect, Tooltip } from "components/ui";
// helper
import { truncateText } from "helpers/string.helper";
// icons
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
// types
@ -64,13 +66,15 @@ export const SidebarModuleSelect: React.FC<Props> = ({
<div className="space-y-1 sm:basis-1/2">
<CustomSelect
label={
<Tooltip position="left" tooltipContent={`${modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"}`}>
<span
className={`w-full max-w-[125px] truncate text-left sm:block ${
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>
</Tooltip>
}
value={issueModule?.module_detail?.id}
onChange={(value: any) => {
@ -80,6 +84,7 @@ export const SidebarModuleSelect: React.FC<Props> = ({
}}
width="w-full"
position="right"
maxHeight="rg"
disabled={isNotAllowed}
>
{modules ? (
@ -88,7 +93,7 @@ export const SidebarModuleSelect: React.FC<Props> = ({
{modules.map((option) => (
<CustomSelect.Option key={option.id} value={option.id}>
<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>
</CustomSelect.Option>
))}

View File

@ -598,7 +598,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
</form>
)}
</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">
<h4>Links</h4>
{!isNotAllowed && (

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { forwardRef, useEffect } from "react";
import { useRouter } from "next/router";
@ -31,12 +31,12 @@ const defaultValues: Partial<IIssueLabels> = {
color: "#ff0000",
};
export const CreateUpdateLabelInline: React.FC<Props> = ({
labelForm,
setLabelForm,
isUpdating,
labelToUpdate,
}) => {
type Ref = HTMLDivElement;
export const CreateUpdateLabelInline = forwardRef<Ref, Props>(function CreateUpdateLabelInline(
{ labelForm, setLabelForm, isUpdating, labelToUpdate },
ref
) {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -109,9 +109,10 @@ export const CreateUpdateLabelInline: React.FC<Props> = ({
return (
<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"
}`}
ref={ref}
>
<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">
@ -187,4 +188,4 @@ export const CreateUpdateLabelInline: React.FC<Props> = ({
)}
</div>
);
};
});

View File

@ -146,7 +146,7 @@ export const LabelsListModal: React.FC<Props> = ({ isOpen, handleClose, parent }
<span
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: label.color,
backgroundColor: label.color !== "" ? label.color : "#000000",
}}
/>
{label.name}

View File

@ -11,7 +11,14 @@ import issuesService from "services/issues.service";
// ui
import { CustomMenu } from "components/ui";
// icons
import { ChevronDownIcon, RectangleGroupIcon } from "@heroicons/react/24/outline";
import {
ChevronDownIcon,
RectangleGroupIcon,
XMarkIcon,
PlusIcon,
PencilIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
// types
import { IIssueLabels } from "types";
// fetch-keys
@ -72,11 +79,22 @@ export const SingleLabelGroup: React.FC<Props> = ({
<div className="flex items-center gap-2">
<CustomMenu ellipsis>
<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 onClick={() => editLabel(label)}>Edit</CustomMenu.MenuItem>
<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>
<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">
<CustomMenu ellipsis>
<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 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 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>
</div>

View File

@ -4,6 +4,8 @@ import React from "react";
import { CustomMenu } from "components/ui";
// types
import { IIssueLabels } from "types";
//icons
import { RectangleGroupIcon, LinkIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
type Props = {
label: IIssueLabels;
@ -31,11 +33,22 @@ export const SingleLabel: React.FC<Props> = ({
</div>
<CustomMenu ellipsis>
<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 onClick={() => editLabel(label)}>Edit</CustomMenu.MenuItem>
<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>
</div>

View File

@ -25,6 +25,11 @@ type Props = {
export const Workspace: React.FC<Props> = ({ setStep, setWorkspace }) => {
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
const [defaultValues, setDefaultValues] = useState({
name: "",
slug: "",
company_size: null,
});
const { data: invitations, mutate } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
workspaceService.userWorkspaceInvitations()
@ -99,11 +104,13 @@ export const Workspace: React.FC<Props> = ({ setStep, setWorkspace }) => {
setWorkspace(res);
setStep(3);
}}
defaultValues={defaultValues}
setDefaultValues={setDefaultValues}
/>
</Tab.Panel>
<Tab.Panel>
<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.map((invitation) => (
<div key={invitation.id}>

View File

@ -115,7 +115,7 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
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="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">
@ -130,9 +130,9 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete Page - {`"`}
<span className="italic">{data?.name}</span>
{`"`} ? All of the data related to the page will be permanently removed.
Are you sure you want to delete Page- {" "}
<span className="font-bold">{data?.name}</span>
? All of the data related to the page will be permanently removed.
This action cannot be undone.
</p>
</div>

View File

@ -291,9 +291,9 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
<CustomMenu.MenuItem onClick={pushBlockIntoIssues}>
Push into issues
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={editAndPushBlockIntoIssues}>
{/* <CustomMenu.MenuItem onClick={editAndPushBlockIntoIssues}>
Edit and push into issues
</CustomMenu.MenuItem>
</CustomMenu.MenuItem> */}
</>
)}
<CustomMenu.MenuItem onClick={deletePageBlock}>Delete block</CustomMenu.MenuItem>

View File

@ -89,13 +89,13 @@ export const SinglePageDetailedItem: React.FC<TSingleStatProps> = ({
)}
<CustomMenu verticalEllipsis>
<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" />
<span>Edit Page</span>
</span>
</CustomMenu.MenuItem>
<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" />
<span>Delete Page</span>
</span>

View File

@ -102,7 +102,7 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
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" />
<span>Edit Page</span>
</span>
@ -114,7 +114,7 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
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" />
<span>Delete Page</span>
</span>

View File

@ -53,7 +53,7 @@ const ConfirmProjectMemberRemove: React.FC<Props> = ({ isOpen, onClose, data, ha
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
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="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">
@ -68,9 +68,9 @@ const ConfirmProjectMemberRemove: React.FC<Props> = ({ isOpen, onClose, data, ha
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to remove member - {`"`}
<span className="italic">{data?.email}</span>
{`"`} ? They will no longer have access to this project. This action
Are you sure you want to remove member- {" "}
<span className="font-bold">{data?.email}</span>
? They will no longer have access to this project. This action
cannot be undone.
</p>
</div>

View File

@ -104,7 +104,7 @@ export const DeleteStateModal: React.FC<Props> = ({ isOpen, onClose, data }) =>
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
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="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">
@ -119,9 +119,9 @@ export const DeleteStateModal: React.FC<Props> = ({ isOpen, onClose, data }) =>
</Dialog.Title>
<div className="mt-2">
<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>
{`"`} ? 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.
</p>
</div>

View File

@ -96,7 +96,7 @@ export const DeleteViewModal: React.FC<Props> = ({ isOpen, data, onClose, onSucc
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
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="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">
@ -111,9 +111,9 @@ export const DeleteViewModal: React.FC<Props> = ({ isOpen, data, onClose, onSucc
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete view - {`"`}
<span className="italic">{data?.name}</span>
{`?"`} All of the data related to the view will be permanently removed.
Are you sure you want to delete view- {" "}
<span className="font-bold">{data?.name}</span>
? All of the data related to the view will be permanently removed.
This action cannot be undone.
</p>
</div>

View File

@ -20,16 +20,11 @@ import { VIEWS_LIST } from "constants/fetch-keys";
import useToast from "hooks/use-toast";
type Props = {
view: IView,
setSelectedView: React.Dispatch<React.SetStateAction<IView | null>>,
view: IView;
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 { workspaceSlug, projectId } = router.query;
@ -86,36 +81,48 @@ export const SingleViewItem: React.FC<Props> = ({
};
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 justify-between w-full">
<div className="flex items-center gap-2">
<StackedLayersIcon height={18} width={18} />
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
<a>{view.name}</a>
</Link>
</div>
<div className="flex">
{
view.is_favorite ? (
<button type="button" onClick={handleRemoveFromFavorites}>
{view.is_favorite ? (
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleRemoveFromFavorites();
}}
>
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
</button>
) : (
<button type="button" onClick={handleAddToFavorites}>
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleAddToFavorites();
}}
>
<StarIcon className="h-4 w-4 " color="#858E96" />
</button>
)
}
)}
<CustomMenu width="auto" verticalEllipsis>
<CustomMenu.MenuItem
onClick={() => {
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
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" />
<span>Delete</span>
</span>
@ -123,10 +130,14 @@ export const SingleViewItem: React.FC<Props> = ({
</CustomMenu>
</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}
</p>}
</p>
)}
</div>
</div>
)
}
</Link>
</>
);
};

View File

@ -31,7 +31,7 @@ export const CompletedIssuesGraph: React.FC<Props> = ({ month, issues, setMonth
for (let i = 1; i <= weeks; i++) {
data.push({
week_in_month: i,
week_in_month: `Week ${i}`,
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">
<ResponsiveContainer width="100%" height={250}>
<LineChart data={data}>
<CartesianGrid stroke="#e2e2e2" />
<XAxis dataKey="week_in_month" />
<CartesianGrid stroke="#e2e2e280" />
<XAxis dataKey="week_in_month" padding={{ left: 48, right: 48 }} />
<YAxis dataKey="completed_count" allowDecimals={false} />
<Tooltip content={<CustomTooltip />} />
<Line

View File

@ -53,7 +53,7 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
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="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">
@ -68,9 +68,9 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to remove member - {`"`}
<span className="italic">{data?.email}</span>
{`"`} ? They will no longer have access to this workspace. This action
Are you sure you want to remove member- {" "}
<span className="font-bold">{data?.email}</span>
? They will no longer have access to this workspace. This action
cannot be undone.
</p>
</div>

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { mutate } from "swr";
@ -19,15 +19,19 @@ import { COMPANY_SIZE } from "constants/workspace";
type Props = {
onSubmit: (res: IWorkspace) => void;
defaultValues: {
name: string;
slug: string;
company_size: number | null;
};
setDefaultValues: Dispatch<SetStateAction<any>>;
};
const defaultValues = {
name: "",
slug: "",
company_size: null,
};
export const CreateWorkspaceForm: React.FC<Props> = ({ onSubmit }) => {
export const CreateWorkspaceForm: React.FC<Props> = ({
onSubmit,
defaultValues,
setDefaultValues,
}) => {
const [slugError, setSlugError] = useState(false);
const { setToastAlert } = useToast();
@ -37,7 +41,7 @@ export const CreateWorkspaceForm: React.FC<Props> = ({ onSubmit }) => {
handleSubmit,
control,
setValue,
reset,
getValues,
formState: { errors, isSubmitting },
} = useForm<IWorkspace>({ defaultValues });
@ -72,9 +76,13 @@ export const CreateWorkspaceForm: React.FC<Props> = ({ onSubmit }) => {
});
};
useEffect(() => {
reset(defaultValues);
}, [reset]);
useEffect(
() => () => {
// when the component unmounts set the default values to whatever user typed in
setDefaultValues(getValues());
},
[getValues, setDefaultValues]
);
return (
<form

View File

@ -89,19 +89,20 @@ export const timeAgo = (time: any) => {
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";
const now = new Date();
const today = renderDateFormat(new Date());
const now = new Date(today);
const start = new Date(startDate);
const end = new Date(endDate);
if (end < now) {
return "completed";
} else if (start <= now && end >= now) {
if (start <= now && end >= now) {
return "current";
} else {
} else if (start > now) {
return "upcoming";
} else {
return "completed";
}
};
@ -170,4 +171,5 @@ export const renderShortTime = (date: string | Date) => {
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);

View File

@ -118,14 +118,14 @@ const useIssuesView = () => {
[key: string]: IIssue[];
}
| undefined = useMemo(() => {
const issuesToGroup = cycleIssues ?? moduleIssues ?? projectIssues;
const issuesToGroup = cycleId ? cycleIssues : moduleId ? moduleIssues : projectIssues;
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
if (groupByProperty === "state")
return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined;
return issuesToGroup;
}, [projectIssues, cycleIssues, moduleIssues, groupByProperty]);
}, [projectIssues, cycleIssues, moduleIssues, groupByProperty, cycleId, moduleId]);
const isEmpty =
Object.values(groupedByIssues ?? {}).every((group) => group.length === 0) ||

View File

@ -11,8 +11,7 @@ import AppLayout from "layouts/app-layout";
// ui
import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers
import { timeAgo } from "helpers/date-time.helper";
import { Feeds } from "components/core";
// fetch-keys
import { USER_ACTIVITY } from "constants/fetch-keys";
@ -33,21 +32,9 @@ const ProfileActivity = () => {
profilePage
>
{userActivity ? (
<div className="divide-y rounded-[10px] border border-gray-200 bg-white px-6 -mt-4">
{userActivity.results.length > 0
? userActivity.results.map((activity) => (
<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>
userActivity.results.length > 0 ? (
<Feeds activities={userActivity.results} />
) : null
) : (
<Loader className="space-y-5">
<Loader.Item height="40px" />

View File

@ -12,7 +12,6 @@ import { requiredAdmin } from "lib/auth";
import AppLayout from "layouts/app-layout";
// services
import projectService from "services/project.service";
import workspaceService from "services/workspace.service";
// hooks
import useToast from "hooks/use-toast";
// ui
@ -22,7 +21,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { IProject, IWorkspace } from "types";
import type { NextPage, GetServerSidePropsContext } from "next";
// 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 = {
isMember: boolean;
@ -53,8 +52,10 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
);
const { data: people } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS : null,
workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
: null
);
const {
@ -101,7 +102,6 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
console.log(err);
});
};
return (
<AppLayout
memberType={{ isMember, isOwner, isViewer, isGuest }}
@ -140,7 +140,7 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
>
{people?.map((person) => (
<CustomSelect.Option
key={person.id}
key={person.member.id}
value={person.member.id}
className="flex items-center gap-2"
>
@ -200,7 +200,7 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
>
{people?.map((person) => (
<CustomSelect.Option
key={person.id}
key={person.member.id}
value={person.member.id}
className="flex items-center gap-2"
>

View File

@ -68,16 +68,6 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
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(() => {
if (projectDetails)
reset({
@ -88,24 +78,9 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
});
}, [projectDetails, reset]);
const onSubmit = async (formData: IProject) => {
const updateProject = async (payload: Partial<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,
};
await projectService
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
.then(async (res) => {
if (res.exists) setError("identifier", { message: "Identifier already exists" });
else
await projectService
.updateProject(workspaceSlug as string, projectDetails.id, payload)
.then((res) => {
@ -128,7 +103,29 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
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 (

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useRef } from "react";
import { useRouter } from "next/router";
@ -46,6 +46,8 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const scollToRef = useRef<HTMLDivElement>(null);
const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
@ -128,6 +130,7 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
setLabelForm={setLabelForm}
isUpdating={isUpdating}
labelToUpdate={labelToUpdate}
ref={scollToRef}
/>
)}
<>
@ -142,7 +145,12 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
key={label.id}
label={label}
addLabelToGroup={() => addLabelToGroup(label)}
editLabel={editLabel}
editLabel={(label) => {
editLabel(label);
scollToRef.current?.scrollIntoView({
behavior: "smooth",
});
}}
handleLabelDelete={handleLabelDelete}
/>
);
@ -153,7 +161,12 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
label={label}
labelChildren={children}
addLabelToGroup={addLabelToGroup}
editLabel={editLabel}
editLabel={(label) => {
editLabel(label);
scollToRef.current?.scrollIntoView({
behavior: "smooth",
});
}}
handleLabelDelete={handleLabelDelete}
/>
);

View File

@ -21,7 +21,7 @@ import SendProjectInvitationModal from "components/project/send-project-invitati
import { CustomMenu, CustomSelect, Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
// types
import type { NextPage, GetServerSidePropsContext } from "next";
import { UserAuth } from "types";
@ -261,7 +261,10 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
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>
</div>

View File

@ -16,6 +16,9 @@ import AppLayout from "layouts/app-layout";
// ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
//icons
import { PlusIcon } from "components/icons";
// image
import emptyView from "public/empty-state/empty-view.svg";
// fetching keys
@ -64,7 +67,8 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
}
right={
<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
</PrimaryButton>
</div>

View File

@ -16,6 +16,11 @@ import { CreateWorkspaceForm } from "components/workspace";
const CreateWorkspace: NextPage = () => {
const router = useRouter();
const defaultValues = {
name: "",
slug: "",
company_size: null,
};
return (
<DefaultLayout>
@ -24,7 +29,11 @@ const CreateWorkspace: NextPage = () => {
<div className="mb-8 text-center">
<Image src={Logo} height="50" alt="Plane Logo" />
</div>
<CreateWorkspaceForm onSubmit={(res) => router.push(`/${res.slug}`)} />
<CreateWorkspaceForm
defaultValues={defaultValues}
setDefaultValues={() => {}}
onSubmit={(res) => router.push(`/${res.slug}`)}
/>
</div>
</div>
</DefaultLayout>