forked from github/plane
Merge pull request #1156 from makeplane/hot-fix
promote: hot-fix to develop
This commit is contained in:
commit
23d08a2ad1
@ -88,6 +88,25 @@ export const EmailCodeForm = ({ onSuccess }: any) => {
|
|||||||
setErrorResendingCode(false);
|
setErrorResendingCode(false);
|
||||||
}, [emailOld]);
|
}, [emailOld]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const submitForm = (e: KeyboardEvent) => {
|
||||||
|
if (!codeSent && e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit(onSubmit)().then(() => {
|
||||||
|
setResendCodeTimer(30);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!codeSent) {
|
||||||
|
window.addEventListener("keydown", submitForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", submitForm);
|
||||||
|
};
|
||||||
|
}, [handleSubmit, codeSent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className="space-y-5 py-5 px-5">
|
<form className="space-y-5 py-5 px-5">
|
||||||
|
@ -7,6 +7,7 @@ import analyticsService from "services/analytics.service";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
|
import trackEventServices from "services/track-event.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -23,7 +24,13 @@ import { ContrastIcon, LayerDiagonalIcon } from "components/icons";
|
|||||||
// helpers
|
// helpers
|
||||||
import { renderShortDate } from "helpers/date-time.helper";
|
import { renderShortDate } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IProject } from "types";
|
import {
|
||||||
|
IAnalyticsParams,
|
||||||
|
IAnalyticsResponse,
|
||||||
|
IExportAnalyticsFormData,
|
||||||
|
IProject,
|
||||||
|
IWorkspace,
|
||||||
|
} from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ANALYTICS, CYCLE_DETAILS, MODULE_DETAILS, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { ANALYTICS, CYCLE_DETAILS, MODULE_DETAILS, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
@ -82,6 +89,59 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const trackExportAnalytics = () => {
|
||||||
|
const eventPayload: any = {
|
||||||
|
workspaceSlug: workspaceSlug?.toString(),
|
||||||
|
params: {
|
||||||
|
x_axis: params.x_axis,
|
||||||
|
y_axis: params.y_axis,
|
||||||
|
group: params.segment,
|
||||||
|
project: params.project,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (projectDetails) {
|
||||||
|
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||||
|
|
||||||
|
eventPayload.workspaceId = workspaceDetails.id;
|
||||||
|
eventPayload.workspaceName = workspaceDetails.name;
|
||||||
|
eventPayload.projectId = projectDetails.id;
|
||||||
|
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||||
|
eventPayload.projectName = projectDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycleDetails || moduleDetails) {
|
||||||
|
const details = cycleDetails || moduleDetails;
|
||||||
|
|
||||||
|
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||||
|
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||||
|
eventPayload.projectId = details?.project_detail.id;
|
||||||
|
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||||
|
eventPayload.projectName = details?.project_detail.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycleDetails) {
|
||||||
|
eventPayload.cycleId = cycleDetails.id;
|
||||||
|
eventPayload.cycleName = cycleDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleDetails) {
|
||||||
|
eventPayload.moduleId = moduleDetails.id;
|
||||||
|
eventPayload.moduleName = moduleDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEventServices.trackAnalyticsEvent(
|
||||||
|
eventPayload,
|
||||||
|
cycleId
|
||||||
|
? "CYCLE_ANALYTICS_EXPORT"
|
||||||
|
: moduleId
|
||||||
|
? "MODULE_ANALYTICS_EXPORT"
|
||||||
|
: projectId
|
||||||
|
? "PROJECT_ANALYTICS_EXPORT"
|
||||||
|
: "WORKSPACE_ANALYTICS_EXPORT"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const exportAnalytics = () => {
|
const exportAnalytics = () => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
@ -95,13 +155,15 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
|
|
||||||
analyticsService
|
analyticsService
|
||||||
.exportAnalytics(workspaceSlug.toString(), data)
|
.exportAnalytics(workspaceSlug.toString(), data)
|
||||||
.then((res) =>
|
.then((res) => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: res.message,
|
message: res.message,
|
||||||
})
|
});
|
||||||
)
|
|
||||||
|
trackExportAnalytics();
|
||||||
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -13,6 +13,7 @@ import analyticsService from "services/analytics.service";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
|
import trackEventServices from "services/track-event.service";
|
||||||
// components
|
// components
|
||||||
import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
|
import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
|
||||||
// icons
|
// icons
|
||||||
@ -22,7 +23,7 @@ import {
|
|||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams } from "types";
|
import { IAnalyticsParams, IWorkspace } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ANALYTICS, CYCLE_DETAILS, MODULE_DETAILS, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { ANALYTICS, CYCLE_DETAILS, MODULE_DETAILS, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -95,6 +96,50 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const trackAnalyticsEvent = (tab: string) => {
|
||||||
|
const eventPayload: any = {
|
||||||
|
workspaceSlug: workspaceSlug?.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (projectDetails) {
|
||||||
|
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||||
|
|
||||||
|
eventPayload.workspaceId = workspaceDetails.id;
|
||||||
|
eventPayload.workspaceName = workspaceDetails.name;
|
||||||
|
eventPayload.projectId = projectDetails.id;
|
||||||
|
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||||
|
eventPayload.projectName = projectDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycleDetails || moduleDetails) {
|
||||||
|
const details = cycleDetails || moduleDetails;
|
||||||
|
|
||||||
|
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||||
|
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||||
|
eventPayload.projectId = details?.project_detail.id;
|
||||||
|
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||||
|
eventPayload.projectName = details?.project_detail.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycleDetails) {
|
||||||
|
eventPayload.cycleId = cycleDetails.id;
|
||||||
|
eventPayload.cycleName = cycleDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleDetails) {
|
||||||
|
eventPayload.moduleId = moduleDetails.id;
|
||||||
|
eventPayload.moduleName = moduleDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventType =
|
||||||
|
tab === "Scope and Demand" ? "SCOPE_AND_DEMAND_ANALYTICS" : "CUSTOM_ANALYTICS";
|
||||||
|
|
||||||
|
trackEventServices.trackAnalyticsEvent(
|
||||||
|
eventPayload,
|
||||||
|
cycleId ? `CYCLE_${eventType}` : moduleId ? `MODULE_${eventType}` : `PROJECT_${eventType}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
@ -146,6 +191,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
selected ? "bg-brand-surface-2" : ""
|
selected ? "bg-brand-surface-2" : ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
onClick={() => trackAnalyticsEvent(tab)}
|
||||||
>
|
>
|
||||||
{tab}
|
{tab}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
@ -40,20 +40,14 @@ import {
|
|||||||
} from "helpers/date-time.helper";
|
} from "helpers/date-time.helper";
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import {
|
import { ICycle, IIssue } from "types";
|
||||||
CompletedCyclesResponse,
|
|
||||||
CurrentAndUpcomingCyclesResponse,
|
|
||||||
DraftCyclesResponse,
|
|
||||||
ICycle,
|
|
||||||
IIssue,
|
|
||||||
} from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_COMPLETE_LIST,
|
CYCLE_COMPLETE_LIST,
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
CYCLE_CURRENT_LIST,
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
CYCLE_ISSUES,
|
CYCLE_ISSUES,
|
||||||
|
CYCLE_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
type TSingleStatProps = {
|
type TSingleStatProps = {
|
||||||
@ -111,51 +105,18 @@ export const ActiveCycleDetails: React.FC<TSingleStatProps> = ({ cycle, isComple
|
|||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
switch (cycleStatus) {
|
mutate<ICycle[]>(
|
||||||
case "current":
|
CYCLE_CURRENT_LIST(projectId as string),
|
||||||
case "upcoming":
|
(prevData) =>
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
(prevData ?? []).map((c) => ({
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
...c,
|
||||||
(prevData) => ({
|
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||||
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
|
})),
|
||||||
...c,
|
false
|
||||||
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;
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) =>
|
(prevData: any) =>
|
||||||
(prevData ?? []).map((c: any) => ({
|
(prevData ?? []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
@ -180,51 +141,18 @@ export const ActiveCycleDetails: React.FC<TSingleStatProps> = ({ cycle, isComple
|
|||||||
const handleRemoveFromFavorites = () => {
|
const handleRemoveFromFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
switch (cycleStatus) {
|
mutate<ICycle[]>(
|
||||||
case "current":
|
CYCLE_CURRENT_LIST(projectId as string),
|
||||||
case "upcoming":
|
(prevData) =>
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
(prevData ?? []).map((c) => ({
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
...c,
|
||||||
(prevData) => ({
|
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||||
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
|
})),
|
||||||
...c,
|
false
|
||||||
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;
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) =>
|
(prevData: any) =>
|
||||||
(prevData ?? []).map((c: any) => ({
|
(prevData ?? []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
|
@ -38,7 +38,10 @@ export const CompletedCycles: React.FC<CompletedCyclesListProps> = ({
|
|||||||
const { data: completedCycles } = useSWR(
|
const { data: completedCycles } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_COMPLETE_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_COMPLETE_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cyclesService.getCompletedCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cyclesService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "completed",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ export const CompletedCycles: React.FC<CompletedCyclesListProps> = ({
|
|||||||
data={selectedCycleForDelete}
|
data={selectedCycleForDelete}
|
||||||
/>
|
/>
|
||||||
{completedCycles ? (
|
{completedCycles ? (
|
||||||
completedCycles.completed_cycles.length > 0 ? (
|
completedCycles.length > 0 ? (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center gap-2 text-sm text-brand-secondary">
|
<div className="flex items-center gap-2 text-sm text-brand-secondary">
|
||||||
<ExclamationIcon
|
<ExclamationIcon
|
||||||
@ -76,7 +79,7 @@ export const CompletedCycles: React.FC<CompletedCyclesListProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{cycleView === "list" && (
|
{cycleView === "list" && (
|
||||||
<div>
|
<div>
|
||||||
{completedCycles.completed_cycles.map((cycle) => (
|
{completedCycles.map((cycle) => (
|
||||||
<div className="hover:bg-brand-surface-2">
|
<div className="hover:bg-brand-surface-2">
|
||||||
<div className="flex flex-col border-brand-base">
|
<div className="flex flex-col border-brand-base">
|
||||||
<SingleCycleList
|
<SingleCycleList
|
||||||
@ -93,7 +96,7 @@ export const CompletedCycles: React.FC<CompletedCyclesListProps> = ({
|
|||||||
)}
|
)}
|
||||||
{cycleView === "board" && (
|
{cycleView === "board" && (
|
||||||
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{completedCycles.completed_cycles.map((cycle) => (
|
{completedCycles.map((cycle) => (
|
||||||
<SingleCycleCard
|
<SingleCycleCard
|
||||||
key={cycle.id}
|
key={cycle.id}
|
||||||
cycle={cycle}
|
cycle={cycle}
|
||||||
|
@ -4,6 +4,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot } from "components/gantt-chart";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { ICycle } from "types";
|
import { ICycle } from "types";
|
||||||
|
|
||||||
@ -31,9 +33,11 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles }) => {
|
|||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${data?.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${data?.id}`}>
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
||||||
<div className="flex-shrink-0 w-[4px] h-full" style={{ backgroundColor: "#858e96" }} />
|
<div className="flex-shrink-0 w-[4px] h-full" style={{ backgroundColor: "#858e96" }} />
|
||||||
<div className="w-full text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden">
|
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
||||||
{data?.name}
|
<div className="text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
||||||
</div>
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -18,26 +18,23 @@ import { EmptyState, Loader } from "components/ui";
|
|||||||
import { ChartBarIcon, ListBulletIcon, Squares2X2Icon } from "@heroicons/react/24/outline";
|
import { ChartBarIcon, ListBulletIcon, Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||||
import emptyCycle from "public/empty-state/empty-cycle.svg";
|
import emptyCycle from "public/empty-state/empty-cycle.svg";
|
||||||
// types
|
// types
|
||||||
import {
|
import { SelectCycleType, ICycle } from "types";
|
||||||
SelectCycleType,
|
|
||||||
ICycle,
|
|
||||||
CurrentAndUpcomingCyclesResponse,
|
|
||||||
DraftCyclesResponse,
|
|
||||||
} from "types";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setSelectedCycle: React.Dispatch<React.SetStateAction<SelectCycleType>>;
|
setSelectedCycle: React.Dispatch<React.SetStateAction<SelectCycleType>>;
|
||||||
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
|
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
cyclesCompleteList: ICycle[] | undefined;
|
cyclesCompleteList: ICycle[] | undefined;
|
||||||
currentAndUpcomingCycles: CurrentAndUpcomingCyclesResponse | undefined;
|
currentCycle: ICycle[] | undefined;
|
||||||
draftCycles: DraftCyclesResponse | undefined;
|
upcomingCycles: ICycle[] | undefined;
|
||||||
|
draftCycles: ICycle[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CyclesView: React.FC<Props> = ({
|
export const CyclesView: React.FC<Props> = ({
|
||||||
setSelectedCycle,
|
setSelectedCycle,
|
||||||
setCreateUpdateCycleModal,
|
setCreateUpdateCycleModal,
|
||||||
cyclesCompleteList,
|
cyclesCompleteList,
|
||||||
currentAndUpcomingCycles,
|
currentCycle,
|
||||||
|
upcomingCycles,
|
||||||
draftCycles,
|
draftCycles,
|
||||||
}) => {
|
}) => {
|
||||||
const { storedValue: cycleTab, setValue: setCycleTab } = useLocalStorage("cycleTab", "All");
|
const { storedValue: cycleTab, setValue: setCycleTab } = useLocalStorage("cycleTab", "All");
|
||||||
@ -182,8 +179,8 @@ export const CyclesView: React.FC<Props> = ({
|
|||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
{cyclesView !== "gantt_chart" && (
|
{cyclesView !== "gantt_chart" && (
|
||||||
<Tab.Panel as="div" className="mt-7 space-y-5">
|
<Tab.Panel as="div" className="mt-7 space-y-5">
|
||||||
{currentAndUpcomingCycles?.current_cycle?.[0] ? (
|
{currentCycle?.[0] ? (
|
||||||
<ActiveCycleDetails cycle={currentAndUpcomingCycles?.current_cycle?.[0]} />
|
<ActiveCycleDetails cycle={currentCycle?.[0]} />
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
type="cycle"
|
type="cycle"
|
||||||
@ -197,7 +194,7 @@ export const CyclesView: React.FC<Props> = ({
|
|||||||
<Tab.Panel as="div" className="mt-7 space-y-5 h-full overflow-y-auto">
|
<Tab.Panel as="div" className="mt-7 space-y-5 h-full overflow-y-auto">
|
||||||
{cyclesView === "list" && (
|
{cyclesView === "list" && (
|
||||||
<AllCyclesList
|
<AllCyclesList
|
||||||
cycles={currentAndUpcomingCycles?.upcoming_cycle}
|
cycles={upcomingCycles}
|
||||||
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
||||||
setSelectedCycle={setSelectedCycle}
|
setSelectedCycle={setSelectedCycle}
|
||||||
type="upcoming"
|
type="upcoming"
|
||||||
@ -205,14 +202,14 @@ export const CyclesView: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
{cyclesView === "board" && (
|
{cyclesView === "board" && (
|
||||||
<AllCyclesBoard
|
<AllCyclesBoard
|
||||||
cycles={currentAndUpcomingCycles?.upcoming_cycle}
|
cycles={upcomingCycles}
|
||||||
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
||||||
setSelectedCycle={setSelectedCycle}
|
setSelectedCycle={setSelectedCycle}
|
||||||
type="upcoming"
|
type="upcoming"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{cyclesView === "gantt_chart" && (
|
{cyclesView === "gantt_chart" && (
|
||||||
<CyclesListGanttChartView cycles={currentAndUpcomingCycles?.upcoming_cycle ?? []} />
|
<CyclesListGanttChartView cycles={upcomingCycles ?? []} />
|
||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel as="div" className="mt-7 space-y-5">
|
<Tab.Panel as="div" className="mt-7 space-y-5">
|
||||||
@ -226,7 +223,7 @@ export const CyclesView: React.FC<Props> = ({
|
|||||||
<Tab.Panel as="div" className="mt-7 space-y-5">
|
<Tab.Panel as="div" className="mt-7 space-y-5">
|
||||||
{cyclesView === "list" && (
|
{cyclesView === "list" && (
|
||||||
<AllCyclesList
|
<AllCyclesList
|
||||||
cycles={draftCycles?.draft_cycles}
|
cycles={draftCycles}
|
||||||
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
||||||
setSelectedCycle={setSelectedCycle}
|
setSelectedCycle={setSelectedCycle}
|
||||||
type="draft"
|
type="draft"
|
||||||
@ -234,7 +231,7 @@ export const CyclesView: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
{cyclesView === "board" && (
|
{cyclesView === "board" && (
|
||||||
<AllCyclesBoard
|
<AllCyclesBoard
|
||||||
cycles={draftCycles?.draft_cycles}
|
cycles={draftCycles}
|
||||||
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
||||||
setSelectedCycle={setSelectedCycle}
|
setSelectedCycle={setSelectedCycle}
|
||||||
type="draft"
|
type="draft"
|
||||||
|
@ -14,12 +14,7 @@ import { DangerButton, SecondaryButton } from "components/ui";
|
|||||||
// icons
|
// icons
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type {
|
import type { ICycle } from "types";
|
||||||
CompletedCyclesResponse,
|
|
||||||
CurrentAndUpcomingCyclesResponse,
|
|
||||||
DraftCyclesResponse,
|
|
||||||
ICycle,
|
|
||||||
} from "types";
|
|
||||||
type TConfirmCycleDeletionProps = {
|
type TConfirmCycleDeletionProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
@ -28,10 +23,10 @@ type TConfirmCycleDeletionProps = {
|
|||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_COMPLETE_LIST,
|
CYCLE_COMPLETE_LIST,
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
CYCLE_CURRENT_LIST,
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
CYCLE_LIST,
|
CYCLE_LIST,
|
||||||
|
CYCLE_UPCOMING_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||||
|
|
||||||
@ -60,63 +55,28 @@ export const DeleteCycleModal: React.FC<TConfirmCycleDeletionProps> = ({
|
|||||||
await cycleService
|
await cycleService
|
||||||
.deleteCycle(workspaceSlug as string, data.project, data.id)
|
.deleteCycle(workspaceSlug as string, data.project, data.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
switch (getDateRangeStatus(data.start_date, data.end_date)) {
|
const cycleType = getDateRangeStatus(data.start_date, data.end_date);
|
||||||
case "completed":
|
const fetchKey =
|
||||||
mutate<CompletedCyclesResponse>(
|
cycleType === "current"
|
||||||
CYCLE_COMPLETE_LIST(projectId as string),
|
? CYCLE_CURRENT_LIST(projectId as string)
|
||||||
(prevData) => {
|
: cycleType === "upcoming"
|
||||||
if (!prevData) return;
|
? CYCLE_UPCOMING_LIST(projectId as string)
|
||||||
|
: cycleType === "completed"
|
||||||
|
? CYCLE_COMPLETE_LIST(projectId as string)
|
||||||
|
: CYCLE_DRAFT_LIST(projectId as string);
|
||||||
|
|
||||||
return {
|
mutate<ICycle[]>(
|
||||||
completed_cycles: prevData.completed_cycles?.filter(
|
fetchKey,
|
||||||
(cycle) => cycle.id !== data?.id
|
(prevData) => {
|
||||||
),
|
if (!prevData) return;
|
||||||
};
|
|
||||||
},
|
return prevData.filter((cycle) => cycle.id !== data?.id);
|
||||||
false
|
},
|
||||||
);
|
false
|
||||||
break;
|
);
|
||||||
case "current":
|
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return;
|
|
||||||
return {
|
|
||||||
current_cycle: prevData.current_cycle?.filter((c) => c.id !== data?.id),
|
|
||||||
upcoming_cycle: prevData.upcoming_cycle,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "upcoming":
|
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return;
|
|
||||||
|
|
||||||
return {
|
|
||||||
current_cycle: prevData.current_cycle,
|
|
||||||
upcoming_cycle: prevData.upcoming_cycle?.filter((c) => c.id !== data?.id),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mutate<DraftCyclesResponse>(
|
|
||||||
CYCLE_DRAFT_LIST(projectId as string),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return;
|
|
||||||
return {
|
|
||||||
draft_cycles: prevData.draft_cycles?.filter((cycle) => cycle.id !== data?.id),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) => {
|
(prevData: any) => {
|
||||||
if (!prevData) return;
|
if (!prevData) return;
|
||||||
return prevData.filter((cycle: any) => cycle.id !== data?.id);
|
return prevData.filter((cycle: any) => cycle.id !== data?.id);
|
||||||
|
@ -4,6 +4,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot } from "components/gantt-chart";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import useGanttChartCycleIssues from "hooks/gantt-chart/cycle-issues-view";
|
import useGanttChartCycleIssues from "hooks/gantt-chart/cycle-issues-view";
|
||||||
|
|
||||||
@ -38,9 +40,23 @@ export const CycleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
className="flex-shrink-0 w-[4px] h-full"
|
className="flex-shrink-0 w-[4px] h-full"
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
||||||
/>
|
/>
|
||||||
<div className="w-full text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden">
|
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
||||||
{data?.name}
|
<div className="text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
||||||
</div>
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{data.infoToggle && (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
||||||
|
className={`z-[999999]`}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
||||||
|
<span className="material-symbols-rounded text-brand-secondary text-[18px]">
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@ -59,10 +75,20 @@ export const CycleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
const blockFormat = (blocks: any) =>
|
const blockFormat = (blocks: any) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => {
|
? blocks.map((_block: any) => {
|
||||||
if (_block?.start_date && _block.target_date) console.log("_block", _block);
|
let startDate = new Date(_block.created_at);
|
||||||
|
let targetDate = new Date(_block.updated_at);
|
||||||
|
let infoToggle = true;
|
||||||
|
|
||||||
|
if (_block?.start_date && _block.target_date) {
|
||||||
|
startDate = _block?.start_date;
|
||||||
|
targetDate = _block.target_date;
|
||||||
|
infoToggle = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start_date: new Date(_block.created_at),
|
start_date: new Date(startDate),
|
||||||
target_date: new Date(_block.updated_at),
|
target_date: new Date(targetDate),
|
||||||
|
infoToggle: infoToggle,
|
||||||
data: _block,
|
data: _block,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -19,10 +19,11 @@ import type { ICycle } from "types";
|
|||||||
// fetch keys
|
// fetch keys
|
||||||
import {
|
import {
|
||||||
CYCLE_COMPLETE_LIST,
|
CYCLE_COMPLETE_LIST,
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
CYCLE_CURRENT_LIST,
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
CYCLE_INCOMPLETE_LIST,
|
CYCLE_INCOMPLETE_LIST,
|
||||||
|
CYCLE_LIST,
|
||||||
|
CYCLE_UPCOMING_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
type CycleModalProps = {
|
type CycleModalProps = {
|
||||||
@ -50,16 +51,16 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
|||||||
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
case "current":
|
case "current":
|
||||||
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
mutate(CYCLE_CURRENT_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
case "upcoming":
|
case "upcoming":
|
||||||
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
mutate(CYCLE_UPCOMING_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
||||||
}
|
}
|
||||||
mutate(CYCLE_INCOMPLETE_LIST(projectId as string));
|
mutate(CYCLE_INCOMPLETE_LIST(projectId as string));
|
||||||
mutate(CYCLE_DETAILS(projectId as string));
|
mutate(CYCLE_LIST(projectId as string));
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -86,15 +87,15 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
|||||||
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
case "current":
|
case "current":
|
||||||
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
mutate(CYCLE_CURRENT_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
case "upcoming":
|
case "upcoming":
|
||||||
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
mutate(CYCLE_UPCOMING_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
||||||
}
|
}
|
||||||
mutate(CYCLE_DETAILS(projectId as string));
|
mutate(CYCLE_LIST(projectId as string));
|
||||||
if (
|
if (
|
||||||
getDateRangeStatus(data?.start_date, data?.end_date) !=
|
getDateRangeStatus(data?.start_date, data?.end_date) !=
|
||||||
getDateRangeStatus(res.start_date, res.end_date)
|
getDateRangeStatus(res.start_date, res.end_date)
|
||||||
@ -104,10 +105,10 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
|||||||
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
mutate(CYCLE_COMPLETE_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
case "current":
|
case "current":
|
||||||
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
mutate(CYCLE_CURRENT_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
case "upcoming":
|
case "upcoming":
|
||||||
mutate(CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string));
|
mutate(CYCLE_UPCOMING_LIST(projectId as string));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
mutate(CYCLE_DRAFT_LIST(projectId as string));
|
||||||
|
@ -38,7 +38,10 @@ export const CycleSelect: React.FC<IssueCycleSelectProps> = ({
|
|||||||
const { data: cycles } = useSWR(
|
const { data: cycles } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_LIST(projectId) : null,
|
workspaceSlug && projectId ? CYCLE_LIST(projectId) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cycleServices.getCycles(workspaceSlug as string, projectId)
|
? () =>
|
||||||
|
cycleServices.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "all",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,18 +41,14 @@ import {
|
|||||||
} from "helpers/date-time.helper";
|
} from "helpers/date-time.helper";
|
||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import {
|
import { ICycle } from "types";
|
||||||
CompletedCyclesResponse,
|
|
||||||
CurrentAndUpcomingCyclesResponse,
|
|
||||||
DraftCyclesResponse,
|
|
||||||
ICycle,
|
|
||||||
} from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_COMPLETE_LIST,
|
CYCLE_COMPLETE_LIST,
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
CYCLE_CURRENT_LIST,
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
|
CYCLE_LIST,
|
||||||
|
CYCLE_UPCOMING_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
type TSingleStatProps = {
|
type TSingleStatProps = {
|
||||||
@ -108,51 +104,27 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
switch (cycleStatus) {
|
const fetchKey =
|
||||||
case "current":
|
cycleStatus === "current"
|
||||||
case "upcoming":
|
? CYCLE_CURRENT_LIST(projectId as string)
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
: cycleStatus === "upcoming"
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
? CYCLE_UPCOMING_LIST(projectId as string)
|
||||||
(prevData) => ({
|
: cycleStatus === "completed"
|
||||||
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
|
? CYCLE_COMPLETE_LIST(projectId as string)
|
||||||
...c,
|
: CYCLE_DRAFT_LIST(projectId as string);
|
||||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
|
||||||
})),
|
mutate<ICycle[]>(
|
||||||
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
|
fetchKey,
|
||||||
...c,
|
(prevData) =>
|
||||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
(prevData ?? []).map((c) => ({
|
||||||
})),
|
...c,
|
||||||
}),
|
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||||
false
|
})),
|
||||||
);
|
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;
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) =>
|
(prevData: any) =>
|
||||||
(prevData ?? []).map((c: any) => ({
|
(prevData ?? []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
@ -177,51 +149,27 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
const handleRemoveFromFavorites = () => {
|
const handleRemoveFromFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
switch (cycleStatus) {
|
const fetchKey =
|
||||||
case "current":
|
cycleStatus === "current"
|
||||||
case "upcoming":
|
? CYCLE_CURRENT_LIST(projectId as string)
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
: cycleStatus === "upcoming"
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
? CYCLE_UPCOMING_LIST(projectId as string)
|
||||||
(prevData) => ({
|
: cycleStatus === "completed"
|
||||||
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
|
? CYCLE_COMPLETE_LIST(projectId as string)
|
||||||
...c,
|
: CYCLE_DRAFT_LIST(projectId as string);
|
||||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
|
||||||
})),
|
mutate<ICycle[]>(
|
||||||
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
|
fetchKey,
|
||||||
...c,
|
(prevData) =>
|
||||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
(prevData ?? []).map((c) => ({
|
||||||
})),
|
...c,
|
||||||
}),
|
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||||
false
|
})),
|
||||||
);
|
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;
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) =>
|
(prevData: any) =>
|
||||||
(prevData ?? []).map((c: any) => ({
|
(prevData ?? []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
|
@ -32,18 +32,14 @@ import {
|
|||||||
} from "helpers/date-time.helper";
|
} from "helpers/date-time.helper";
|
||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import {
|
import { ICycle } from "types";
|
||||||
CompletedCyclesResponse,
|
|
||||||
CurrentAndUpcomingCyclesResponse,
|
|
||||||
DraftCyclesResponse,
|
|
||||||
ICycle,
|
|
||||||
} from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_COMPLETE_LIST,
|
CYCLE_COMPLETE_LIST,
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
CYCLE_CURRENT_LIST,
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
|
CYCLE_LIST,
|
||||||
|
CYCLE_UPCOMING_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
import { type } from "os";
|
import { type } from "os";
|
||||||
|
|
||||||
@ -142,51 +138,27 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
switch (cycleStatus) {
|
const fetchKey =
|
||||||
case "current":
|
cycleStatus === "current"
|
||||||
case "upcoming":
|
? CYCLE_CURRENT_LIST(projectId as string)
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
: cycleStatus === "upcoming"
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
? CYCLE_UPCOMING_LIST(projectId as string)
|
||||||
(prevData) => ({
|
: cycleStatus === "completed"
|
||||||
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
|
? CYCLE_COMPLETE_LIST(projectId as string)
|
||||||
...c,
|
: CYCLE_DRAFT_LIST(projectId as string);
|
||||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
|
||||||
})),
|
mutate<ICycle[]>(
|
||||||
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
|
fetchKey,
|
||||||
...c,
|
(prevData) =>
|
||||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
(prevData ?? []).map((c) => ({
|
||||||
})),
|
...c,
|
||||||
}),
|
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||||
false
|
})),
|
||||||
);
|
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;
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) =>
|
(prevData: any) =>
|
||||||
(prevData ?? []).map((c: any) => ({
|
(prevData ?? []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
@ -211,51 +183,27 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
const handleRemoveFromFavorites = () => {
|
const handleRemoveFromFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycle) return;
|
if (!workspaceSlug || !projectId || !cycle) return;
|
||||||
|
|
||||||
switch (cycleStatus) {
|
const fetchKey =
|
||||||
case "current":
|
cycleStatus === "current"
|
||||||
case "upcoming":
|
? CYCLE_CURRENT_LIST(projectId as string)
|
||||||
mutate<CurrentAndUpcomingCyclesResponse>(
|
: cycleStatus === "upcoming"
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
|
? CYCLE_UPCOMING_LIST(projectId as string)
|
||||||
(prevData) => ({
|
: cycleStatus === "completed"
|
||||||
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
|
? CYCLE_COMPLETE_LIST(projectId as string)
|
||||||
...c,
|
: CYCLE_DRAFT_LIST(projectId as string);
|
||||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
|
||||||
})),
|
mutate<ICycle[]>(
|
||||||
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
|
fetchKey,
|
||||||
...c,
|
(prevData) =>
|
||||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
(prevData ?? []).map((c) => ({
|
||||||
})),
|
...c,
|
||||||
}),
|
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||||
false
|
})),
|
||||||
);
|
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;
|
|
||||||
}
|
|
||||||
mutate(
|
mutate(
|
||||||
CYCLE_DETAILS(projectId as string),
|
CYCLE_LIST(projectId as string),
|
||||||
(prevData: any) =>
|
(prevData: any) =>
|
||||||
(prevData ?? []).map((c: any) => ({
|
(prevData ?? []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
|
@ -59,7 +59,10 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
const { data: incompleteCycles } = useSWR(
|
const { data: incompleteCycles } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_INCOMPLETE_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_INCOMPLETE_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cyclesService.getIncompleteCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cyclesService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "incomplete",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -49,7 +49,10 @@ export const GanttChartBlocks: FC<{
|
|||||||
width: `${block?.position?.width}px`,
|
width: `${block?.position?.width}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{blockRender({ ...block?.data })}
|
{blockRender({
|
||||||
|
...block?.data,
|
||||||
|
infoToggle: block?.infoToggle ? true : false,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-shrink-0 relative w-0 h-0 flex items-center invisible group-hover:visible whitespace-nowrap">
|
<div className="flex-shrink-0 relative w-0 h-0 flex items-center invisible group-hover:visible whitespace-nowrap">
|
||||||
|
@ -4,6 +4,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot } from "components/gantt-chart";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
|
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
|
||||||
|
|
||||||
@ -37,9 +39,23 @@ export const IssueGanttChartView: FC<Props> = ({}) => {
|
|||||||
className="flex-shrink-0 w-[4px] h-full"
|
className="flex-shrink-0 w-[4px] h-full"
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
||||||
/>
|
/>
|
||||||
<div className="w-full text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden">
|
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
||||||
{data?.name}
|
<div className="text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
||||||
</div>
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{data.infoToggle && (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
||||||
|
className={`z-[999999]`}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
||||||
|
<span className="material-symbols-rounded text-brand-secondary text-[18px]">
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@ -51,17 +67,25 @@ export const IssueGanttChartView: FC<Props> = ({}) => {
|
|||||||
start_date: data?.start_date,
|
start_date: data?.start_date,
|
||||||
target_date: data?.target_date,
|
target_date: data?.target_date,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("payload", payload);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
const blockFormat = (blocks: any) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => {
|
? blocks.map((_block: any) => {
|
||||||
if (_block?.start_date && _block.target_date) console.log("_block", _block);
|
let startDate = new Date(_block.created_at);
|
||||||
|
let targetDate = new Date(_block.updated_at);
|
||||||
|
let infoToggle = true;
|
||||||
|
|
||||||
|
if (_block?.start_date && _block.target_date) {
|
||||||
|
startDate = _block?.start_date;
|
||||||
|
targetDate = _block.target_date;
|
||||||
|
infoToggle = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start_date: new Date(_block.created_at),
|
start_date: new Date(startDate),
|
||||||
target_date: new Date(_block.updated_at),
|
target_date: new Date(targetDate),
|
||||||
|
infoToggle: infoToggle,
|
||||||
data: _block,
|
data: _block,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,10 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
|||||||
const { data: incompleteCycles } = useSWR(
|
const { data: incompleteCycles } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_INCOMPLETE_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_INCOMPLETE_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cyclesService.getIncompleteCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cyclesService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "incomplete",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot } from "components/gantt-chart";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import useGanttChartModuleIssues from "hooks/gantt-chart/module-issues-view";
|
import useGanttChartModuleIssues from "hooks/gantt-chart/module-issues-view";
|
||||||
|
|
||||||
@ -38,9 +40,23 @@ export const ModuleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
className="flex-shrink-0 w-[4px] h-full"
|
className="flex-shrink-0 w-[4px] h-full"
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
||||||
/>
|
/>
|
||||||
<div className="w-full text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden">
|
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
||||||
{data?.name}
|
<div className="text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
||||||
</div>
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{data.infoToggle && (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
||||||
|
className={`z-[999999]`}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
||||||
|
<span className="material-symbols-rounded text-brand-secondary text-[18px]">
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@ -59,10 +75,20 @@ export const ModuleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
const blockFormat = (blocks: any) =>
|
const blockFormat = (blocks: any) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => {
|
? blocks.map((_block: any) => {
|
||||||
if (_block?.start_date && _block.target_date) console.log("_block", _block);
|
let startDate = new Date(_block.created_at);
|
||||||
|
let targetDate = new Date(_block.updated_at);
|
||||||
|
let infoToggle = true;
|
||||||
|
|
||||||
|
if (_block?.start_date && _block.target_date) {
|
||||||
|
startDate = _block?.start_date;
|
||||||
|
targetDate = _block.target_date;
|
||||||
|
infoToggle = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start_date: new Date(_block.created_at),
|
start_date: new Date(startDate),
|
||||||
target_date: new Date(_block.updated_at),
|
target_date: new Date(targetDate),
|
||||||
|
infoToggle: infoToggle,
|
||||||
data: _block,
|
data: _block,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot } from "components/gantt-chart";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { IModule } from "types";
|
import { IModule } from "types";
|
||||||
// constants
|
// constants
|
||||||
@ -38,9 +40,11 @@ export const ModulesListGanttChartView: FC<Props> = ({ modules }) => {
|
|||||||
className="flex-shrink-0 w-[4px] h-full"
|
className="flex-shrink-0 w-[4px] h-full"
|
||||||
style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === data.status)?.color }}
|
style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === data.status)?.color }}
|
||||||
/>
|
/>
|
||||||
<div className="w-full text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden">
|
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
||||||
{data?.name}
|
<div className="text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
||||||
</div>
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,10 @@ export const AllPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? ALL_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? ALL_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => pagesService.getAllPages(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
page_view: "all",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -18,7 +18,10 @@ export const FavoritePagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? FAVORITE_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? FAVORITE_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => pagesService.getFavoritePages(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
page_view: "favorite",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -18,7 +18,10 @@ export const MyPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? MY_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? MY_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => pagesService.getMyPages(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
page_view: "created_by_me",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -18,7 +18,10 @@ export const OtherPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? OTHER_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? OTHER_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => pagesService.getOtherPages(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
page_view: "created_by_other",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -162,14 +162,22 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
|
|||||||
input
|
input
|
||||||
width="w-full"
|
width="w-full"
|
||||||
>
|
>
|
||||||
{uninvitedPeople?.map((person) => (
|
{uninvitedPeople && uninvitedPeople.length > 0 ? (
|
||||||
<CustomSelect.Option
|
<>
|
||||||
key={person.member.id}
|
{uninvitedPeople?.map((person) => (
|
||||||
value={person.member.id}
|
<CustomSelect.Option
|
||||||
>
|
key={person.member.id}
|
||||||
{person.member.email}
|
value={person.member.id}
|
||||||
</CustomSelect.Option>
|
>
|
||||||
))}
|
{person.member.email}
|
||||||
|
</CustomSelect.Option>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-sm py-5">
|
||||||
|
Invite members to workspace before adding them to a project.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot } from "components/gantt-chart";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
|
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
|
||||||
|
|
||||||
@ -38,9 +40,23 @@ export const ViewIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
className="flex-shrink-0 w-[4px] h-full"
|
className="flex-shrink-0 w-[4px] h-full"
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
|
||||||
/>
|
/>
|
||||||
<div className="w-full text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden">
|
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
||||||
{data?.name}
|
<div className="text-brand-base text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
||||||
</div>
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{data.infoToggle && (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
||||||
|
className={`z-[999999]`}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
||||||
|
<span className="material-symbols-rounded text-brand-secondary text-[18px]">
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@ -59,10 +75,20 @@ export const ViewIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
const blockFormat = (blocks: any) =>
|
const blockFormat = (blocks: any) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => {
|
? blocks.map((_block: any) => {
|
||||||
if (_block?.start_date && _block.target_date) console.log("_block", _block);
|
let startDate = new Date(_block.created_at);
|
||||||
|
let targetDate = new Date(_block.updated_at);
|
||||||
|
let infoToggle = true;
|
||||||
|
|
||||||
|
if (_block?.start_date && _block.target_date) {
|
||||||
|
startDate = _block?.start_date;
|
||||||
|
targetDate = _block.target_date;
|
||||||
|
infoToggle = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start_date: new Date(_block.created_at),
|
start_date: new Date(startDate),
|
||||||
target_date: new Date(_block.updated_at),
|
target_date: new Date(targetDate),
|
||||||
|
infoToggle: infoToggle,
|
||||||
data: _block,
|
data: _block,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -83,8 +83,11 @@ export const CYCLE_ISSUES_WITH_PARAMS = (cycleId: string, params?: any) => {
|
|||||||
return `CYCLE_ISSUES_WITH_PARAMS_${cycleId.toUpperCase()}_${paramsKey.toUpperCase()}`;
|
return `CYCLE_ISSUES_WITH_PARAMS_${cycleId.toUpperCase()}_${paramsKey.toUpperCase()}`;
|
||||||
};
|
};
|
||||||
export const CYCLE_DETAILS = (cycleId: string) => `CYCLE_DETAILS_${cycleId.toUpperCase()}`;
|
export const CYCLE_DETAILS = (cycleId: string) => `CYCLE_DETAILS_${cycleId.toUpperCase()}`;
|
||||||
export const CYCLE_CURRENT_AND_UPCOMING_LIST = (projectId: string) =>
|
|
||||||
`CYCLE_CURRENT_AND_UPCOMING_LIST_${projectId.toUpperCase()}`;
|
export const CYCLE_CURRENT_LIST = (projectId: string) =>
|
||||||
|
`CYCLE_CURRENT_LIST${projectId.toUpperCase()}`;
|
||||||
|
export const CYCLE_UPCOMING_LIST = (projectId: string) =>
|
||||||
|
`CYCLE_UPCOMING_LIST${projectId.toUpperCase()}`;
|
||||||
export const CYCLE_DRAFT_LIST = (projectId: string) =>
|
export const CYCLE_DRAFT_LIST = (projectId: string) =>
|
||||||
`CYCLE_DRAFT_LIST_${projectId.toUpperCase()}`;
|
`CYCLE_DRAFT_LIST_${projectId.toUpperCase()}`;
|
||||||
export const CYCLE_COMPLETE_LIST = (projectId: string) =>
|
export const CYCLE_COMPLETE_LIST = (projectId: string) =>
|
||||||
|
@ -8,10 +8,12 @@ export const groupBy = (array: any[], key: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const orderArrayBy = (
|
export const orderArrayBy = (
|
||||||
array: any[],
|
orgArray: any[],
|
||||||
key: string,
|
key: string,
|
||||||
ordering: "ascending" | "descending" = "ascending"
|
ordering: "ascending" | "descending" = "ascending"
|
||||||
) => {
|
) => {
|
||||||
|
const array = [...orgArray];
|
||||||
|
|
||||||
if (!array || !Array.isArray(array) || array.length === 0) return [];
|
if (!array || !Array.isArray(array) || array.length === 0) return [];
|
||||||
|
|
||||||
if (key[0] === "-") {
|
if (key[0] === "-") {
|
||||||
|
@ -1,26 +1,18 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { useRouter } from "next/router";
|
import useSWR, { mutate } from "swr";
|
||||||
import useSWR from "swr";
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import userService from "services/user.service";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// helpers
|
|
||||||
import { groupBy } from "helpers/array.helper";
|
|
||||||
import { getStatesList } from "helpers/state.helper";
|
|
||||||
// types
|
// types
|
||||||
import { Properties, NestedKeyOf, IIssue } from "types";
|
import { IWorkspaceMember, Properties } from "types";
|
||||||
// fetch-keys
|
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
|
||||||
import { STATES_LIST } from "constants/fetch-keys";
|
|
||||||
// constants
|
|
||||||
import { PRIORITIES } from "constants/project";
|
|
||||||
|
|
||||||
const initialValues: Properties = {
|
const initialValues: Properties = {
|
||||||
assignee: true,
|
assignee: true,
|
||||||
due_date: false,
|
due_date: false,
|
||||||
key: true,
|
key: true,
|
||||||
labels: true,
|
labels: false,
|
||||||
priority: false,
|
priority: false,
|
||||||
state: true,
|
state: true,
|
||||||
sub_issue_count: false,
|
sub_issue_count: false,
|
||||||
@ -29,99 +21,80 @@ const initialValues: Properties = {
|
|||||||
estimate: false,
|
estimate: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Refactor this logic
|
const useMyIssuesProperties = (workspaceSlug?: string) => {
|
||||||
const useMyIssuesProperties = (issues?: IIssue[]) => {
|
|
||||||
const [properties, setProperties] = useState<Properties>(initialValues);
|
const [properties, setProperties] = useState<Properties>(initialValues);
|
||||||
const [groupByProperty, setGroupByProperty] = useState<NestedKeyOf<IIssue> | null>(null);
|
|
||||||
|
|
||||||
// FIXME: where this hook is used we may not have project id in the url
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: myWorkspace } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug ? () => workspaceService.workspaceMemberMe(workspaceSlug as string) : null,
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
{
|
||||||
: null
|
shouldRetryOnError: false,
|
||||||
);
|
|
||||||
const states = getStatesList(stateGroups ?? {});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user) return;
|
|
||||||
setProperties({ ...initialValues, ...user.my_issues_prop?.properties });
|
|
||||||
setGroupByProperty(user.my_issues_prop?.groupBy ?? null);
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
const groupedByIssues: {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
} = {
|
|
||||||
...(groupByProperty === "state_detail.name"
|
|
||||||
? Object.fromEntries(
|
|
||||||
states
|
|
||||||
?.sort((a, b) => a.sequence - b.sequence)
|
|
||||||
?.map((state) => [
|
|
||||||
state.name,
|
|
||||||
issues?.filter((issue) => issue.state === state.name) ?? [],
|
|
||||||
]) ?? []
|
|
||||||
)
|
|
||||||
: groupByProperty === "priority"
|
|
||||||
? Object.fromEntries(
|
|
||||||
PRIORITIES.map((priority) => [
|
|
||||||
priority,
|
|
||||||
issues?.filter((issue) => issue.priority === priority) ?? [],
|
|
||||||
])
|
|
||||||
)
|
|
||||||
: {}),
|
|
||||||
...groupBy(issues ?? [], groupByProperty ?? ""),
|
|
||||||
};
|
|
||||||
|
|
||||||
const setMyIssueProperty = (key: keyof Properties) => {
|
|
||||||
if (!user) return;
|
|
||||||
userService.updateUser({ my_issues_prop: { properties, groupBy: groupByProperty } });
|
|
||||||
setProperties((prevData) => ({
|
|
||||||
...prevData,
|
|
||||||
[key]: !prevData[key],
|
|
||||||
}));
|
|
||||||
localStorage.setItem(
|
|
||||||
"my_issues_prop",
|
|
||||||
JSON.stringify({
|
|
||||||
properties: {
|
|
||||||
...properties,
|
|
||||||
[key]: !properties[key],
|
|
||||||
},
|
|
||||||
groupBy: groupByProperty,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setMyIssueGroupByProperty = (groupByProperty: NestedKeyOf<IIssue> | null) => {
|
|
||||||
if (!user) return;
|
|
||||||
userService.updateUser({ my_issues_prop: { properties, groupBy: groupByProperty } });
|
|
||||||
setGroupByProperty(groupByProperty);
|
|
||||||
localStorage.setItem(
|
|
||||||
"my_issues_prop",
|
|
||||||
JSON.stringify({ properties, groupBy: groupByProperty })
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const viewProps = localStorage.getItem("my_issues_prop");
|
|
||||||
if (viewProps) {
|
|
||||||
const { properties, groupBy } = JSON.parse(viewProps);
|
|
||||||
setProperties(properties);
|
|
||||||
setGroupByProperty(groupBy);
|
|
||||||
}
|
}
|
||||||
}, []);
|
);
|
||||||
|
|
||||||
return {
|
useEffect(() => {
|
||||||
filteredIssues: groupedByIssues,
|
if (!myWorkspace || !workspaceSlug || !user) return;
|
||||||
groupByProperty,
|
|
||||||
properties,
|
setProperties({ ...initialValues, ...myWorkspace.view_props });
|
||||||
setMyIssueProperty,
|
|
||||||
setMyIssueGroupByProperty,
|
if (!myWorkspace.view_props) {
|
||||||
} as const;
|
workspaceService.updateWorkspaceView(workspaceSlug, {
|
||||||
|
view_props: { ...initialValues },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [myWorkspace, workspaceSlug, user]);
|
||||||
|
|
||||||
|
const updateIssueProperties = useCallback(
|
||||||
|
(key: keyof Properties) => {
|
||||||
|
if (!workspaceSlug || !user) return;
|
||||||
|
|
||||||
|
setProperties((prev) => ({ ...prev, [key]: !prev[key] }));
|
||||||
|
|
||||||
|
if (myWorkspace) {
|
||||||
|
mutate<IWorkspaceMember>(
|
||||||
|
WORKSPACE_MEMBERS_ME(workspaceSlug.toString()),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return;
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
view_props: { ...prevData?.view_props, [key]: !prevData.view_props?.[key] },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
if (myWorkspace.view_props) {
|
||||||
|
workspaceService.updateWorkspaceView(workspaceSlug, {
|
||||||
|
view_props: {
|
||||||
|
...myWorkspace.view_props,
|
||||||
|
[key]: !myWorkspace.view_props[key],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
workspaceService.updateWorkspaceView(workspaceSlug, {
|
||||||
|
view_props: { ...initialValues },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[workspaceSlug, myWorkspace, user]
|
||||||
|
);
|
||||||
|
|
||||||
|
const newProperties: Properties = {
|
||||||
|
assignee: properties.assignee,
|
||||||
|
due_date: properties.due_date,
|
||||||
|
key: properties.key,
|
||||||
|
labels: properties.labels,
|
||||||
|
priority: properties.priority,
|
||||||
|
state: properties.state,
|
||||||
|
sub_issue_count: properties.sub_issue_count,
|
||||||
|
attachment_count: properties.attachment_count,
|
||||||
|
link: properties.link,
|
||||||
|
estimate: properties.estimate,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [newProperties, updateIssueProperties] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useMyIssuesProperties;
|
export default useMyIssuesProperties;
|
||||||
|
@ -19,9 +19,9 @@ const useProjects = () => {
|
|||||||
workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null
|
workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const recentProjects = projects
|
const recentProjects = [...(projects ?? [])]
|
||||||
?.sort((a, b) => Date.parse(`${a.updated_at}`) - Date.parse(`${b.updated_at}`))
|
?.sort((a, b) => Date.parse(`${a.updated_at}`) - Date.parse(`${b.updated_at}`))
|
||||||
.filter((_item, index) => index < 3);
|
?.slice(0, 3);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects: orderArrayBy(projects ?? [], "is_favorite", "descending") || [],
|
projects: orderArrayBy(projects ?? [], "is_favorite", "descending") || [],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment } from "react";
|
import React, { Fragment, useEffect } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import analyticsService from "services/analytics.service";
|
import analyticsService from "services/analytics.service";
|
||||||
|
import trackEventServices from "services/track-event.service";
|
||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
@ -48,6 +49,28 @@ const Analytics = () => {
|
|||||||
workspaceSlug ? () => analyticsService.getAnalytics(workspaceSlug.toString(), params) : null
|
workspaceSlug ? () => analyticsService.getAnalytics(workspaceSlug.toString(), params) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const trackAnalyticsEvent = (tab: string) => {
|
||||||
|
const eventPayload = {
|
||||||
|
workspaceSlug: workspaceSlug?.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventType =
|
||||||
|
tab === "Scope and Demand"
|
||||||
|
? "WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS"
|
||||||
|
: "WORKSPACE_CUSTOM_ANALYTICS";
|
||||||
|
|
||||||
|
trackEventServices.trackAnalyticsEvent(eventPayload, eventType);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
trackEventServices.trackAnalyticsEvent(
|
||||||
|
{ workspaceSlug: workspaceSlug?.toString() },
|
||||||
|
"WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS"
|
||||||
|
);
|
||||||
|
}, [workspaceSlug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceAuthorizationLayout
|
<WorkspaceAuthorizationLayout
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
@ -79,6 +102,7 @@ const Analytics = () => {
|
|||||||
selected ? "bg-brand-surface-2" : ""
|
selected ? "bg-brand-surface-2" : ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
onClick={() => trackAnalyticsEvent(tab)}
|
||||||
>
|
>
|
||||||
{tab}
|
{tab}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
@ -14,7 +14,7 @@ import useIssues from "hooks/use-issues";
|
|||||||
import { Spinner, EmptySpace, EmptySpaceItem, PrimaryButton } from "components/ui";
|
import { Spinner, EmptySpace, EmptySpaceItem, PrimaryButton } from "components/ui";
|
||||||
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
|
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesProperties from "hooks/use-issue-properties";
|
import useMyIssuesProperties from "hooks/use-my-issues-filter";
|
||||||
// types
|
// types
|
||||||
import { IIssue, Properties } from "types";
|
import { IIssue, Properties } from "types";
|
||||||
// components
|
// components
|
||||||
@ -31,10 +31,7 @@ const MyIssuesPage: NextPage = () => {
|
|||||||
// fetching user issues
|
// fetching user issues
|
||||||
const { myIssues } = useIssues(workspaceSlug as string);
|
const { myIssues } = useIssues(workspaceSlug as string);
|
||||||
|
|
||||||
const [properties, setProperties] = useIssuesProperties(
|
const [properties, setProperties] = useMyIssuesProperties(workspaceSlug as string);
|
||||||
workspaceSlug ? (workspaceSlug as string) : undefined,
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceAuthorizationLayout
|
<WorkspaceAuthorizationLayout
|
||||||
|
@ -56,7 +56,10 @@ const SingleCycle: React.FC = () => {
|
|||||||
const { data: cycles } = useSWR(
|
const { data: cycles } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cycleServices.getCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cycleServices.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "all",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -21,10 +21,11 @@ import { SelectCycleType } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
PROJECT_DETAILS,
|
PROJECT_DETAILS,
|
||||||
CYCLE_DETAILS,
|
CYCLE_UPCOMING_LIST,
|
||||||
|
CYCLE_CURRENT_LIST,
|
||||||
|
CYCLE_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
const ProjectCycles: NextPage = () => {
|
const ProjectCycles: NextPage = () => {
|
||||||
@ -44,21 +45,40 @@ const ProjectCycles: NextPage = () => {
|
|||||||
const { data: draftCycles } = useSWR(
|
const { data: draftCycles } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_DRAFT_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_DRAFT_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cycleService.getDraftCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "draft",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: currentAndUpcomingCycles } = useSWR(
|
const { data: currentCycle } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_CURRENT_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cycleService.getCurrentAndUpcomingCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "current",
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: upcomingCycles } = useSWR(
|
||||||
|
workspaceSlug && projectId ? CYCLE_UPCOMING_LIST(projectId as string) : null,
|
||||||
|
workspaceSlug && projectId
|
||||||
|
? () =>
|
||||||
|
cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "upcoming",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: cyclesCompleteList } = useSWR(
|
const { data: cyclesCompleteList } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLE_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? CYCLE_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => cycleService.getCycles(workspaceSlug as string, projectId as string)
|
? () =>
|
||||||
|
cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, {
|
||||||
|
cycle_view: "all",
|
||||||
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -121,7 +141,8 @@ const ProjectCycles: NextPage = () => {
|
|||||||
setSelectedCycle={setSelectedCycle}
|
setSelectedCycle={setSelectedCycle}
|
||||||
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
|
||||||
cyclesCompleteList={cyclesCompleteList}
|
cyclesCompleteList={cyclesCompleteList}
|
||||||
currentAndUpcomingCycles={currentAndUpcomingCycles}
|
currentCycle={currentCycle}
|
||||||
|
upcomingCycles={upcomingCycles}
|
||||||
draftCycles={draftCycles}
|
draftCycles={draftCycles}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,6 +66,7 @@ const MembersSettings: NextPage = () => {
|
|||||||
role: item.role,
|
role: item.role,
|
||||||
status: true,
|
status: true,
|
||||||
member: true,
|
member: true,
|
||||||
|
accountCreated: true,
|
||||||
})) || []),
|
})) || []),
|
||||||
...(workspaceInvitations?.map((item) => ({
|
...(workspaceInvitations?.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@ -77,6 +78,7 @@ const MembersSettings: NextPage = () => {
|
|||||||
role: item.role,
|
role: item.role,
|
||||||
status: item.accepted,
|
status: item.accepted,
|
||||||
member: false,
|
member: false,
|
||||||
|
accountCreated: item?.accepted ? false : true,
|
||||||
})) || []),
|
})) || []),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -200,6 +202,11 @@ const MembersSettings: NextPage = () => {
|
|||||||
<p>Pending</p>
|
<p>Pending</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{member?.status && !member?.accountCreated && (
|
||||||
|
<div className="mr-2 flex items-center justify-center rounded-full bg-blue-500/20 px-2 py-1 text-center text-xs text-blue-500">
|
||||||
|
<p>Account not created</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label={ROLE[member.role as keyof typeof ROLE]}
|
label={ROLE[member.role as keyof typeof ROLE]}
|
||||||
value={member.role}
|
value={member.role}
|
||||||
|
@ -9,7 +9,6 @@ axios.interceptors.response.use(
|
|||||||
if (unAuthorizedStatus.includes(status)) {
|
if (unAuthorizedStatus.includes(status)) {
|
||||||
Cookies.remove("refreshToken", { path: "/" });
|
Cookies.remove("refreshToken", { path: "/" });
|
||||||
Cookies.remove("accessToken", { path: "/" });
|
Cookies.remove("accessToken", { path: "/" });
|
||||||
console.log("window.location.href", window.location.pathname);
|
|
||||||
if (window.location.pathname != "/signin") window.location.href = "/signin";
|
if (window.location.pathname != "/signin") window.location.href = "/signin";
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -3,15 +3,7 @@ import APIService from "services/api.service";
|
|||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track-event.service";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import type {
|
import type { ICycle, IIssue, IIssueViewOptions } from "types";
|
||||||
CycleIssueResponse,
|
|
||||||
CompletedCyclesResponse,
|
|
||||||
CurrentAndUpcomingCyclesResponse,
|
|
||||||
DraftCyclesResponse,
|
|
||||||
ICycle,
|
|
||||||
IIssue,
|
|
||||||
IIssueViewOptions,
|
|
||||||
} from "types";
|
|
||||||
|
|
||||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||||
|
|
||||||
@ -34,16 +26,14 @@ class ProjectCycleServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCycles(workspaceSlug: string, projectId: string): Promise<ICycle[]> {
|
async getCyclesWithParams(
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`)
|
workspaceSlug: string,
|
||||||
.then((response) => response?.data)
|
projectId: string,
|
||||||
.catch((error) => {
|
queries: any
|
||||||
throw error?.response?.data;
|
): Promise<ICycle[]> {
|
||||||
});
|
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, {
|
||||||
}
|
params: queries,
|
||||||
|
})
|
||||||
async getIncompleteCycles(workspaceSlug: string, projectId: string): Promise<ICycle[]> {
|
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/incomplete-cycles/`)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
@ -159,40 +149,6 @@ class ProjectCycleServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentAndUpcomingCycles(
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string
|
|
||||||
): Promise<CurrentAndUpcomingCyclesResponse> {
|
|
||||||
return this.get(
|
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/current-upcoming-cycles/`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDraftCycles(workspaceSlug: string, projectId: string): Promise<DraftCyclesResponse> {
|
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/draft-cycles/`)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCompletedCycles(
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string
|
|
||||||
): Promise<CompletedCyclesResponse> {
|
|
||||||
return this.get(
|
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/completed-cycles/`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async addCycleToFavorites(
|
async addCycleToFavorites(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
@ -83,42 +83,26 @@ class PageServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPagesWithParams(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
queries: any
|
||||||
|
): Promise<IPage[]> {
|
||||||
|
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`, {
|
||||||
|
params: queries,
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getRecentPages(workspaceSlug: string, projectId: string): Promise<RecentPagesResponse> {
|
async getRecentPages(workspaceSlug: string, projectId: string): Promise<RecentPagesResponse> {
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/recent-pages/`)
|
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`, {
|
||||||
.then((response) => response?.data)
|
params: {
|
||||||
.catch((error) => {
|
page_view: "recent",
|
||||||
throw error?.response?.data;
|
},
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
async getAllPages(workspaceSlug: string, projectId: string): Promise<IPage[]> {
|
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFavoritePages(workspaceSlug: string, projectId: string): Promise<IPage[]> {
|
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/favorite-pages/`)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMyPages(workspaceSlug: string, projectId: string): Promise<IPage[]> {
|
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/my-pages/`)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOtherPages(workspaceSlug: string, projectId: string): Promise<IPage[]> {
|
|
||||||
return this.get(
|
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/created-by-other-pages/`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
|
@ -84,6 +84,21 @@ type ImporterEventType =
|
|||||||
| "GITHUB_IMPORTER_DELETE"
|
| "GITHUB_IMPORTER_DELETE"
|
||||||
| "JIRA_IMPORTER_CREATE"
|
| "JIRA_IMPORTER_CREATE"
|
||||||
| "JIRA_IMPORTER_DELETE";
|
| "JIRA_IMPORTER_DELETE";
|
||||||
|
|
||||||
|
type AnalyticsEventType =
|
||||||
|
| "WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS"
|
||||||
|
| "WORKSPACE_CUSTOM_ANALYTICS"
|
||||||
|
| "WORKSPACE_ANALYTICS_EXPORT"
|
||||||
|
| "PROJECT_SCOPE_AND_DEMAND_ANALYTICS"
|
||||||
|
| "PROJECT_CUSTOM_ANALYTICS"
|
||||||
|
| "PROJECT_ANALYTICS_EXPORT"
|
||||||
|
| "CYCLE_SCOPE_AND_DEMAND_ANALYTICS"
|
||||||
|
| "CYCLE_CUSTOM_ANALYTICS"
|
||||||
|
| "CYCLE_ANALYTICS_EXPORT"
|
||||||
|
| "MODULE_SCOPE_AND_DEMAND_ANALYTICS"
|
||||||
|
| "MODULE_CUSTOM_ANALYTICS"
|
||||||
|
| "MODULE_ANALYTICS_EXPORT";
|
||||||
|
|
||||||
class TrackEventServices extends APIService {
|
class TrackEventServices extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("/");
|
super("/");
|
||||||
@ -615,6 +630,19 @@ class TrackEventServices extends APIService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async trackAnalyticsEvent(data: any, eventName: AnalyticsEventType): Promise<any> {
|
||||||
|
const payload = { ...data };
|
||||||
|
|
||||||
|
return this.request({
|
||||||
|
url: "/api/track-event",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
eventName,
|
||||||
|
extra: payload,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackEventServices = new TrackEventServices();
|
const trackEventServices = new TrackEventServices();
|
||||||
|
@ -142,6 +142,14 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateWorkspaceView(workspaceSlug: string, data: any): Promise<any> {
|
||||||
|
return this.post(`/api/workspaces/${workspaceSlug}/workspace-views/`, data)
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async updateWorkspaceMember(
|
async updateWorkspaceMember(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
memberId: string,
|
memberId: string,
|
||||||
|
13
apps/app/types/cycles.d.ts
vendored
13
apps/app/types/cycles.d.ts
vendored
@ -38,19 +38,6 @@ export interface ICycle {
|
|||||||
workspace_detail: IWorkspaceLite;
|
workspace_detail: IWorkspaceLite;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrentAndUpcomingCyclesResponse {
|
|
||||||
current_cycle: ICycle[];
|
|
||||||
upcoming_cycle: ICycle[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DraftCyclesResponse {
|
|
||||||
draft_cycles: ICycle[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CompletedCyclesResponse {
|
|
||||||
completed_cycles: ICycle[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CycleIssueResponse {
|
export interface CycleIssueResponse {
|
||||||
id: string;
|
id: string;
|
||||||
issue_detail: IIssue;
|
issue_detail: IIssue;
|
||||||
|
14
apps/app/types/workspace.d.ts
vendored
14
apps/app/types/workspace.d.ts
vendored
@ -33,6 +33,19 @@ export interface IWorkspaceMemberInvitation {
|
|||||||
workspace: IWorkspace;
|
workspace: IWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Properties = {
|
||||||
|
assignee: boolean;
|
||||||
|
due_date: boolean;
|
||||||
|
labels: boolean;
|
||||||
|
key: boolean;
|
||||||
|
priority: boolean;
|
||||||
|
state: boolean;
|
||||||
|
sub_issue_count: boolean;
|
||||||
|
link: boolean;
|
||||||
|
attachment_count: boolean;
|
||||||
|
estimate: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export interface IWorkspaceMember {
|
export interface IWorkspaceMember {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
user: IUserLite;
|
user: IUserLite;
|
||||||
@ -40,6 +53,7 @@ export interface IWorkspaceMember {
|
|||||||
member: IUserLite;
|
member: IUserLite;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: 5 | 10 | 15 | 20;
|
||||||
company_role: string | null;
|
company_role: string | null;
|
||||||
|
view_props: Properties;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user