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: branches:
- 'develop' - 'develop'
- 'master' - 'master'
tags:
- '*'
jobs: jobs:
build_push_frontend: build_push_frontend:

View File

@ -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={{

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" ? "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={() =>

View File

@ -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";

View File

@ -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(

View File

@ -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">

View File

@ -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

View File

@ -77,7 +77,7 @@ 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(res.start_date, res.end_date)) { switch (getDateRangeStatus(data?.start_date, data?.end_date)) {
case "completed": case "completed":
mutate(CYCLE_COMPLETE_LIST(projectId as string)); mutate(CYCLE_COMPLETE_LIST(projectId as string));
break; break;
@ -90,6 +90,25 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
default: default:
mutate(CYCLE_DRAFT_LIST(projectId as string)); 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));
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));
}
}
handleClose(); handleClose();
setToastAlert({ setToastAlert({

View File

@ -90,59 +90,58 @@ 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;
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
switch (cycleStatus) {
case "current":
case "upcoming":
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
break;
case "completed":
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
completed_cycles: (prevData?.completed_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
break;
case "draft":
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
draft_cycles: (prevData?.draft_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
break;
}
cyclesService cyclesService
.addCycleToFavorites(workspaceSlug as string, projectId as string, { .addCycleToFavorites(workspaceSlug as string, projectId as string, {
cycle: cycle.id, cycle: cycle.id,
}) })
.then(() => {
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
if (cycleStatus === "current" || cycleStatus === "upcoming")
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
else if (cycleStatus === "completed")
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
completed_cycles: (prevData?.completed_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
else
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
draft_cycles: (prevData?.draft_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
setToastAlert({
type: "success",
title: "Success!",
message: "Successfully added the cycle to favorites.",
});
})
.catch(() => { .catch(() => {
setToastAlert({ setToastAlert({
type: "error", type: "error",
@ -153,57 +152,56 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
}; };
const handleRemoveFromFavorites = () => { const handleRemoveFromFavorites = () => {
if (!workspaceSlug || !cycle) return; if (!workspaceSlug || !projectId || !cycle) return;
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
switch (cycleStatus) {
case "current":
case "upcoming":
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
break;
case "completed":
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
completed_cycles: (prevData?.completed_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
break;
case "draft":
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
draft_cycles: (prevData?.draft_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
break;
}
cyclesService cyclesService
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id) .removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
.then(() => {
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
if (cycleStatus === "current" || cycleStatus === "upcoming")
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
else if (cycleStatus === "completed")
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
completed_cycles: (prevData?.completed_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
else
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
draft_cycles: (prevData?.draft_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
setToastAlert({
type: "success",
title: "Success!",
message: "Successfully removed the cycle from favorites.",
});
})
.catch(() => { .catch(() => {
setToastAlert({ setToastAlert({
type: "error", type: "error",

View File

@ -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 dont 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>

View File

@ -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":

View File

@ -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") {

View File

@ -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")}

View File

@ -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={
<span <Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : ""}`}>
className={`w-full max-w-[125px] truncate text-left sm:block ${ <span
issueCycle ? "" : "text-gray-900" className={`w-full max-w-[125px] truncate text-left sm:block ${
}`} issueCycle ? "" : "text-gray-900"
> }`}
{issueCycle ? issueCycle.cycle_detail.name : "None"} >
</span> {issueCycle ? truncateText(issueCycle.cycle_detail.name, 15) : "None"}
</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>
))} ))}

View File

@ -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>
))} ))}

View File

@ -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 && (

View File

@ -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>
); );
}; });

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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}>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

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" 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>

View File

@ -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,47 +81,63 @@ 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> </div>
</Link> <div className="flex">
</div> {view.is_favorite ? (
<div className="flex"> <button
{ type="button"
view.is_favorite ? ( onClick={(e) => {
<button type="button" onClick={handleRemoveFromFavorites}> e.preventDefault();
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" /> e.stopPropagation();
</button> handleRemoveFromFavorites();
) : ( }}
<button type="button" onClick={handleAddToFavorites}> >
<StarIcon className="h-4 w-4 " color="#858E96" /> <StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
</button> </button>
) ) : (
} <button
<CustomMenu width="auto" verticalEllipsis> type="button"
<CustomMenu.MenuItem onClick={(e) => {
onClick={() => { e.preventDefault();
setSelectedView(view); e.stopPropagation();
}} handleAddToFavorites();
> }}
<span className="flex items-center justify-start gap-2 text-gray-800"> >
<TrashIcon className="h-4 w-4" /> <StarIcon className="h-4 w-4 " color="#858E96" />
<span>Delete</span> </button>
</span> )}
</CustomMenu.MenuItem> <CustomMenu width="auto" verticalEllipsis>
</CustomMenu> <CustomMenu.MenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setSelectedView(view);
}}
>
<span className="flex items-center justify-start gap-2">
<TrashIcon className="h-4 w-4" />
<span>Delete</span>
</span>
</CustomMenu.MenuItem>
</CustomMenu>
</div>
</div>
{view?.description && (
<p className="text-sm text-[#858E96] font-normal leading-5 px-[27px]">
{view.description}
</p>
)}
</div> </div>
</div> </div>
{view?.description && <p className="text-sm text-[#858E96] font-normal leading-5 px-[27px]"> </Link>
{view.description} </>
</p>} );
</div> };
</div>
)
}

View File

@ -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

View File

@ -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>

View File

@ -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

View File

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

View File

@ -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) ||

View File

@ -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" />

View File

@ -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"
> >

View File

@ -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,6 +78,33 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
}); });
}, [projectDetails, reset]); }, [projectDetails, reset]);
const updateProject = async (payload: Partial<IProject>) => {
if (!workspaceSlug || !projectDetails) return;
await projectService
.updateProject(workspaceSlug as string, projectDetails.id, payload)
.then((res) => {
mutate<IProject>(
PROJECT_DETAILS(projectDetails.id),
(prevData) => ({ ...prevData, ...res }),
false
);
mutate(PROJECTS_LIST(workspaceSlug as string));
setToastAlert({
type: "success",
title: "Success!",
message: "Project updated successfully",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Project could not be updated. Please try again.",
});
});
};
const onSubmit = async (formData: IProject) => { const onSubmit = async (formData: IProject) => {
if (!workspaceSlug || !projectDetails) return; if (!workspaceSlug || !projectDetails) return;
@ -101,34 +118,14 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
icon: formData.icon, icon: formData.icon,
}; };
await projectService if (projectDetails.identifier !== formData.identifier)
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "") await projectService
.then(async (res) => { .checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
if (res.exists) setError("identifier", { message: "Identifier already exists" }); .then(async (res) => {
else if (res.exists) setError("identifier", { message: "Identifier already exists" });
await projectService else await updateProject(payload);
.updateProject(workspaceSlug as string, projectDetails.id, payload) });
.then((res) => { else await updateProject(payload);
mutate<IProject>(
PROJECT_DETAILS(projectDetails.id),
(prevData) => ({ ...prevData, ...res }),
false
);
mutate(PROJECTS_LIST(workspaceSlug as string));
setToastAlert({
type: "success",
title: "Success!",
message: "Project updated successfully",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Project could not be updated. Please try again.",
});
});
});
}; };
return ( return (

View File

@ -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}
/> />
); );

View File

@ -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>

View File

@ -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>

View File

@ -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>