chore: project cycle bug fixes and improvement (#3427)

* chore: burndown chart's completed at changes

* chore: project cycle bug fixes and improvement

* chore: cycle state constant updated

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
Anmol Singh Bhatia 2024-01-22 20:42:09 +05:30 committed by GitHub
parent 49452a68ab
commit fd5326dec6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 149 additions and 212 deletions

View File

@ -9,6 +9,7 @@ from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import timezone
# Module imports # Module imports
from . import ProjectBaseModel from . import ProjectBaseModel
@ -183,6 +184,17 @@ class Issue(ProjectBaseModel):
self.state = default_state self.state = default_state
except ImportError: except ImportError:
pass pass
else:
try:
from plane.db.models import State
# Check if the current issue state group is completed or not
if self.state.group == "completed":
self.completed_at = timezone.now()
else:
self.completed_at = None
except ImportError:
pass
if self._state.adding: if self._state.adding:
# Get the maximum display_id value from the database # Get the maximum display_id value from the database

View File

@ -4,6 +4,7 @@ from datetime import timedelta
# Django import # Django import
from django.db import models from django.db import models
from django.utils import timezone
from django.db.models.functions import TruncDate from django.db.models.functions import TruncDate
from django.db.models import Count, F, Sum, Value, Case, When, CharField from django.db.models import Count, F, Sum, Value, Case, When, CharField
from django.db.models.functions import ( from django.db.models.functions import (
@ -168,6 +169,9 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None):
if item["date"] is not None and item["date"] <= date if item["date"] is not None and item["date"] <= date
) )
cumulative_pending_issues -= total_completed cumulative_pending_issues -= total_completed
if date > timezone.now().date():
chart_data[str(date)] = None
else:
chart_data[str(date)] = cumulative_pending_issues chart_data[str(date)] = cumulative_pending_issues
return chart_data return chart_data

View File

@ -1,4 +1,11 @@
import type { IUser, TIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "@plane/types"; import type {
IUser,
TIssue,
IProjectLite,
IWorkspaceLite,
IIssueFilterOptions,
IUserLite,
} from "@plane/types";
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft"; export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
@ -54,7 +61,7 @@ export type TAssigneesDistribution = {
}; };
export type TCompletionChartDistribution = { export type TCompletionChartDistribution = {
[key: string]: number; [key: string]: number | null;
}; };
export type TLabelsDistribution = { export type TLabelsDistribution = {
@ -80,9 +87,13 @@ export interface CycleIssueResponse {
sub_issues_count: number; sub_issues_count: number;
} }
export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined; export type SelectCycleType =
| (ICycle & { actionType: "edit" | "delete" | "create-issue" })
| undefined;
export type SelectIssue = (TIssue & { actionType: "edit" | "delete" | "create" }) | null; export type SelectIssue =
| (TIssue & { actionType: "edit" | "delete" | "create" })
| null;
export type CycleDateCheckData = { export type CycleDateCheckData = {
start_date: string; start_date: string;

View File

@ -4,15 +4,17 @@ import { Tooltip } from "../tooltip";
type Props = { type Props = {
data: any; data: any;
noTooltip?: boolean; noTooltip?: boolean;
inPercentage?: boolean;
}; };
export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = false }) => { export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = false, inPercentage = false }) => {
const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0); const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0);
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
let progress = 0; let progress = 0;
const bars = data.map((item: any) => { const bars = data.map((item: any) => {
const width = `${(item.value / total) * 100}%`; const width = `${(item.value / total) * 100}%`;
if (width === "0%") return <></>;
const style = { const style = {
width, width,
backgroundColor: item.color, backgroundColor: item.color,
@ -22,7 +24,7 @@ export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = fal
else else
return ( return (
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}%`}> <Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}%`}>
<div style={style} /> <div style={style} className="first:rounded-l-full last:rounded-r-full" />
</Tooltip> </Tooltip>
); );
}); });

View File

@ -30,7 +30,7 @@ export const SingleProgressStats: React.FC<TSingleProgressStatsProps> = ({
<CircularProgressIndicator percentage={(completed / total) * 100} size={14} strokeWidth={2} /> <CircularProgressIndicator percentage={(completed / total) * 100} size={14} strokeWidth={2} />
</span> </span>
<span className="w-8 text-right"> <span className="w-8 text-right">
{isNaN(Math.floor((completed / total) * 100)) ? "0" : Math.floor((completed / total) * 100)}% {isNaN(Math.round((completed / total) * 100)) ? "0" : Math.round((completed / total) * 100)}%
</span> </span>
</div> </div>
<span>of {total}</span> <span>of {total}</span>

View File

@ -3,7 +3,7 @@ import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
// hooks // hooks
import { useApplication, useCycle, useIssues, useProjectState } from "hooks/store"; import { useApplication, useCycle, useIssues, useProject } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { SingleProgressStats } from "components/core"; import { SingleProgressStats } from "components/core";
@ -12,55 +12,27 @@ import {
Loader, Loader,
Tooltip, Tooltip,
LinearProgressIndicator, LinearProgressIndicator,
ContrastIcon,
RunningIcon,
LayersIcon, LayersIcon,
StateGroupIcon, StateGroupIcon,
PriorityIcon, PriorityIcon,
Avatar, Avatar,
CycleGroupIcon,
} from "@plane/ui"; } from "@plane/ui";
// components // components
import ProgressChart from "components/core/sidebar/progress-chart"; import ProgressChart from "components/core/sidebar/progress-chart";
import { ActiveCycleProgressStats } from "components/cycles"; import { ActiveCycleProgressStats } from "components/cycles";
import { ViewIssueLabel } from "components/issues"; import { StateDropdown } from "components/dropdowns";
// icons // icons
import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; import { ArrowRight, CalendarCheck, CalendarDays, Star, Target } from "lucide-react";
// helpers // helpers
import { renderFormattedDate, findHowManyDaysLeft } from "helpers/date-time.helper"; import { renderFormattedDate, findHowManyDaysLeft, renderFormattedDateWithoutYear } from "helpers/date-time.helper";
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
// types // types
import { ICycle } from "@plane/types"; import { ICycle, TCycleGroups } from "@plane/types";
// constants
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { ACTIVE_CYCLE_ISSUES } from "store/issue/cycle";
import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
import { CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle";
const stateGroups = [
{
key: "backlog_issues",
title: "Backlog",
color: "#dee2e6",
},
{
key: "unstarted_issues",
title: "Unstarted",
color: "#26b5ce",
},
{
key: "started_issues",
title: "Started",
color: "#f7ae59",
},
{
key: "cancelled_issues",
title: "Cancelled",
color: "#d687ff",
},
{
key: "completed_issues",
title: "Completed",
color: "#09a953",
},
];
interface IActiveCycleDetails { interface IActiveCycleDetails {
workspaceSlug: string; workspaceSlug: string;
@ -72,8 +44,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
const { workspaceSlug, projectId } = props; const { workspaceSlug, projectId } = props;
// store hooks // store hooks
const { const {
issues: { issues, fetchActiveCycleIssues }, issues: { fetchActiveCycleIssues },
issueMap,
} = useIssues(EIssuesStoreType.CYCLE); } = useIssues(EIssuesStoreType.CYCLE);
const { const {
commandPalette: { toggleCreateCycleModal }, commandPalette: { toggleCreateCycleModal },
@ -85,7 +56,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
addCycleToFavorites, addCycleToFavorites,
removeCycleFromFavorites, removeCycleFromFavorites,
} = useCycle(); } = useCycle();
const { getProjectStates } = useProjectState(); const { currentProjectDetails } = useProject();
// toast alert // toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -95,9 +66,8 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
); );
const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null; const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null;
const issueIds = issues?.[ACTIVE_CYCLE_ISSUES];
useSWR( const { data: activeCycleIssues } = useSWR(
workspaceSlug && projectId && currentProjectActiveCycleId workspaceSlug && projectId && currentProjectActiveCycleId
? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" }) ? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" })
: null, : null,
@ -149,7 +119,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
cancelled: activeCycle.cancelled_issues, cancelled: activeCycle.cancelled_issues,
}; };
const cycleStatus = activeCycle.status.toLocaleLowerCase(); const cycleStatus = activeCycle.status.toLowerCase() as TCycleGroups;
const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => { const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
@ -177,7 +147,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
}); });
}; };
const progressIndicatorData = stateGroups.map((group, index) => ({ const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({
id: index, id: index,
name: group.title, name: group.title,
value: value:
@ -187,6 +157,8 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
color: group.color, color: group.color,
})); }));
const daysLeft = findHowManyDaysLeft(activeCycle.end_date ?? new Date());
return ( return (
<div className="grid-row-2 grid divide-y rounded-[10px] border border-custom-border-200 bg-custom-background-100 shadow"> <div className="grid-row-2 grid divide-y rounded-[10px] border border-custom-border-200 bg-custom-background-100 shadow">
<div className="grid grid-cols-1 divide-y border-custom-border-200 lg:grid-cols-3 lg:divide-x lg:divide-y-0"> <div className="grid grid-cols-1 divide-y border-custom-border-200 lg:grid-cols-3 lg:divide-x lg:divide-y-0">
@ -196,68 +168,15 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<div className="flex items-center justify-between gap-1"> <div className="flex items-center justify-between gap-1">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<span className="h-5 w-5"> <span className="h-5 w-5">
<ContrastIcon <CycleGroupIcon cycleGroup={cycleStatus} className="h-4 w-4" />
className="h-5 w-5"
color={`${
cycleStatus === "current"
? "#09A953"
: cycleStatus === "upcoming"
? "#F7AE59"
: cycleStatus === "completed"
? "#3F76FF"
: cycleStatus === "draft"
? "rgb(var(--color-text-200))"
: ""
}`}
/>
</span> </span>
<Tooltip tooltipContent={activeCycle.name} position="top-left"> <Tooltip tooltipContent={activeCycle.name} position="top-left">
<h3 className="break-words text-lg font-semibold">{truncateText(activeCycle.name, 70)}</h3> <h3 className="break-words text-lg font-semibold">{truncateText(activeCycle.name, 70)}</h3>
</Tooltip> </Tooltip>
</span> </span>
<span className="flex items-center gap-1 capitalize"> <span className="flex items-center gap-1 capitalize">
<span <span className="flex gap-1 whitespace-nowrap rounded-sm text-sm px-3 py-0.5 bg-amber-500/10 text-amber-500">
className={`rounded-full px-1.5 py-0.5 {`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`}
${
cycleStatus === "current"
? "bg-green-600/5 text-green-600"
: cycleStatus === "upcoming"
? "bg-orange-300/5 text-orange-300"
: cycleStatus === "completed"
? "bg-blue-500/5 text-blue-500"
: cycleStatus === "draft"
? "bg-neutral-400/5 text-neutral-400"
: ""
}`}
>
{cycleStatus === "current" ? (
<span className="flex gap-1 whitespace-nowrap">
<RunningIcon className="h-4 w-4" />
{findHowManyDaysLeft(activeCycle.end_date ?? new Date())} Days Left
</span>
) : cycleStatus === "upcoming" ? (
<span className="flex gap-1 whitespace-nowrap">
<AlarmClock className="h-4 w-4" />
{findHowManyDaysLeft(activeCycle.start_date ?? new Date())} Days Left
</span>
) : cycleStatus === "completed" ? (
<span className="flex gap-1 whitespace-nowrap">
{activeCycle.total_issues - activeCycle.completed_issues > 0 && (
<Tooltip
tooltipContent={`${activeCycle.total_issues - activeCycle.completed_issues} more pending ${
activeCycle.total_issues - activeCycle.completed_issues === 1 ? "issue" : "issues"
}`}
>
<span>
<AlertTriangle className="h-3.5 w-3.5" />
</span>
</Tooltip>
)}{" "}
Completed
</span>
) : (
cycleStatus
)}
</span> </span>
{activeCycle.is_favorite ? ( {activeCycle.is_favorite ? (
<button <button
@ -344,7 +263,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<div className="flex h-full w-full flex-col p-4 text-custom-text-200"> <div className="flex h-full w-full flex-col p-4 text-custom-text-200">
<div className="flex w-full items-center gap-2 py-1"> <div className="flex w-full items-center gap-2 py-1">
<span>Progress</span> <span>Progress</span>
<LinearProgressIndicator data={progressIndicatorData} /> <LinearProgressIndicator data={progressIndicatorData} inPercentage />
</div> </div>
<div className="mt-2 flex flex-col items-center gap-1"> <div className="mt-2 flex flex-col items-center gap-1">
{Object.keys(groupedIssues).map((group, index) => ( {Object.keys(groupedIssues).map((group, index) => (
@ -355,7 +274,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<span <span
className="block h-3 w-3 rounded-full " className="block h-3 w-3 rounded-full "
style={{ style={{
backgroundColor: stateGroups[index].color, backgroundColor: CYCLE_STATE_GROUPS_DETAILS[index].color,
}} }}
/> />
<span className="text-xs capitalize">{group}</span> <span className="text-xs capitalize">{group}</span>
@ -374,63 +293,54 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
</div> </div>
</div> </div>
<div className="grid grid-cols-1 divide-y border-custom-border-200 lg:grid-cols-2 lg:divide-x lg:divide-y-0"> <div className="grid grid-cols-1 divide-y border-custom-border-200 lg:grid-cols-2 lg:divide-x lg:divide-y-0">
<div className="flex flex-col justify-between p-4"> <div className="flex flex-col gap-3 p-4 max-h-60 overflow-hidden">
<div>
<div className="text-custom-primary">High Priority Issues</div> <div className="text-custom-primary">High Priority Issues</div>
<div className="my-3 flex max-h-[240px] min-h-[240px] flex-col gap-2.5 overflow-y-scroll rounded-md"> <div className="flex flex-col h-full gap-2.5 overflow-y-scroll rounded-md">
{issueIds ? ( {activeCycleIssues ? (
issueIds.length > 0 ? ( activeCycleIssues.length > 0 ? (
issueIds.map((issue: any) => ( activeCycleIssues.map((issue: any) => (
<Link <Link
key={issue.id} key={issue.id}
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`} href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
className="flex cursor-pointer flex-wrap items-center justify-between gap-2 rounded-md border border-custom-border-200 bg-custom-background-90 px-3 py-1.5" className="flex cursor-pointer flex-wrap items-center justify-between gap-2 rounded-md border border-custom-border-200 px-3 py-1.5"
> >
<div className="flex flex-col gap-1"> <div className="flex items-center gap-1.5">
<div> <PriorityIcon priority={issue.priority} withContainer size={12} />
<Tooltip <Tooltip
tooltipHeading="Issue ID" tooltipHeading="Issue ID"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`} tooltipContent={`${currentProjectDetails?.identifier}-${issue.sequence_id}`}
> >
<span className="flex-shrink-0 text-xs text-custom-text-200"> <span className="flex-shrink-0 text-xs text-custom-text-200">
{issue.project_detail?.identifier}-{issue.sequence_id} {currentProjectDetails?.identifier}-{issue.sequence_id}
</span> </span>
</Tooltip> </Tooltip>
</div>
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}> <Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
<span className="text-[0.825rem] text-custom-text-100">{truncateText(issue.name, 30)}</span> <span className="text-[0.825rem] text-custom-text-100">{truncateText(issue.name, 30)}</span>
</Tooltip> </Tooltip>
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5 flex-shrink-0">
<div <StateDropdown
className={`grid h-6 w-6 flex-shrink-0 place-items-center items-center rounded border shadow-sm ${ value={issue.state_id ?? undefined}
issue.priority === "urgent" onChange={() => {}}
? "border-red-500/20 bg-red-500/20 text-red-500" projectId={projectId?.toString() ?? ""}
: "border-orange-500/20 bg-orange-500/20 text-orange-500" disabled={true}
}`} buttonVariant="background-with-text"
> />
<PriorityIcon priority={issue.priority} className="text-sm" /> {issue.target_date && (
<Tooltip tooltipHeading="Target Date" tooltipContent={renderFormattedDate(issue.target_date)}>
<div className="h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 bg-custom-background-80 cursor-not-allowed">
<CalendarCheck className="h-3 w-3 flex-shrink-0" />
<span className="text-xs">{renderFormattedDateWithoutYear(issue.target_date)}</span>
</div> </div>
<ViewIssueLabel labelDetails={issue.label_details} maxRender={2} /> </Tooltip>
<div className={`flex items-center gap-2 text-custom-text-200`}>
{issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
<div className="-my-0.5 flex items-center justify-center gap-2">
<AvatarGroup showTooltip={false}>
{issue.assignee_details.map((assignee: any) => (
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} />
))}
</AvatarGroup>
</div>
) : (
""
)} )}
</div> </div>
</div>
</Link> </Link>
)) ))
) : ( ) : (
<div className="grid place-items-center text-center text-sm text-custom-text-200"> <div className="flex items-center justify-center h-full text-sm text-custom-text-200">
No issues present in the cycle. There are no high priority issues present in this cycle.
</div> </div>
) )
) : ( ) : (
@ -442,39 +352,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
)} )}
</div> </div>
</div> </div>
<div className="flex flex-col border-custom-border-200 p-4 max-h-60">
{issueIds && issueIds.length > 0 && (
<div className="flex items-center justify-between gap-2">
<div className="h-1 w-full rounded-full bg-custom-background-80">
<div
className="h-1 rounded-full bg-green-600"
style={{
width:
issueIds &&
`${
(issueIds.filter((issue: any) => issue?.state_detail?.group === "completed")?.length /
issueIds.length) *
100 ?? 0
}%`,
}}
/>
</div>
<div className="w-16 text-end text-xs text-custom-text-200">
of{" "}
{
issueIds?.filter(
(issueId) =>
getProjectStates(issueMap[issueId]?.project_id)?.find(
(issue) => issue.id === issueMap[issueId]?.state_id
)?.group === "completed"
)?.length
}{" "}
of {issueIds?.length}
</div>
</div>
)}
</div>
<div className="flex flex-col justify-between border-custom-border-200 p-4">
<div className="flex items-start justify-between gap-4 py-1.5 text-xs"> <div className="flex items-start justify-between gap-4 py-1.5 text-xs">
<div className="flex items-center gap-3 text-custom-text-100"> <div className="flex items-center gap-3 text-custom-text-100">
<div className="flex items-center justify-center gap-1"> <div className="flex items-center justify-center gap-1">
@ -496,7 +374,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
</span> </span>
</div> </div>
</div> </div>
<div className="relative h-64"> <div className="relative h-full">
<ProgressChart <ProgressChart
distribution={activeCycle.distribution?.completion_chart ?? {}} distribution={activeCycle.distribution?.completion_chart ?? {}}
startDate={activeCycle.start_date ?? ""} startDate={activeCycle.start_date ?? ""}

View File

@ -127,7 +127,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
</Tab.Panels> </Tab.Panels>
) : ( ) : (
<div className="mt-4 grid place-items-center text-center text-sm text-custom-text-200"> <div className="mt-4 grid place-items-center text-center text-sm text-custom-text-200">
No issues present in the cycle. There are no high priority issues present in this cycle.
</div> </div>
)} )}
</Tab.Group> </Tab.Group>

View File

@ -126,6 +126,7 @@ export const CycleForm: React.FC<Props> = (props) => {
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)} onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
buttonVariant="border-with-text" buttonVariant="border-with-text"
placeholder="Start date" placeholder="Start date"
minDate={new Date()}
maxDate={maxDate ?? undefined} maxDate={maxDate ?? undefined}
tabIndex={3} tabIndex={3}
/> />

View File

@ -9,7 +9,7 @@ type Props = {
export const ViewIssueLabel: React.FC<Props> = ({ labelDetails, maxRender = 1 }) => ( export const ViewIssueLabel: React.FC<Props> = ({ labelDetails, maxRender = 1 }) => (
<> <>
{labelDetails.length > 0 ? ( {labelDetails?.length > 0 ? (
labelDetails.length <= maxRender ? ( labelDetails.length <= maxRender ? (
<> <>
{labelDetails.map((label) => ( {labelDetails.map((label) => (

View File

@ -70,7 +70,7 @@ export const ModuleForm: React.FC<Props> = ({
const startDate = watch("start_date"); const startDate = watch("start_date");
const targetDate = watch("target_date"); const targetDate = watch("target_date");
const minDate = startDate ? new Date(startDate) : null; const minDate = startDate ? new Date(startDate) : new Date();
minDate?.setDate(minDate.getDate()); minDate?.setDate(minDate.getDate());
const maxDate = targetDate ? new Date(targetDate) : null; const maxDate = targetDate ? new Date(targetDate) : null;
@ -159,6 +159,7 @@ export const ModuleForm: React.FC<Props> = ({
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)} onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
buttonVariant="border-with-text" buttonVariant="border-with-text"
placeholder="Start date" placeholder="Start date"
minDate={new Date()}
maxDate={maxDate ?? undefined} maxDate={maxDate ?? undefined}
tabIndex={3} tabIndex={3}
/> />

View File

@ -86,3 +86,31 @@ export const CYCLE_STATUS: {
bgColor: "bg-custom-background-90", bgColor: "bg-custom-background-90",
}, },
]; ];
export const CYCLE_STATE_GROUPS_DETAILS = [
{
key: "backlog_issues",
title: "Backlog",
color: "#F0F0F3",
},
{
key: "unstarted_issues",
title: "Unstarted",
color: "#FB923C",
},
{
key: "started_issues",
title: "Started",
color: "#FFC53D",
},
{
key: "cancelled_issues",
title: "Cancelled",
color: "#d687ff",
},
{
key: "completed_issues",
title: "Completed",
color: "#ef4444",
},
];