mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: cycle and module reordering in the gantt chart (#3570)
* fix: cycle and module reordering in the gantt chart * chore: hide duration from sidebar if no dates are assigned * chore: updated date helper functions to accept undefined params * chore: update cycle sidebar condition
This commit is contained in:
parent
c1c0297b6d
commit
9545dc77d6
@ -150,7 +150,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
color: group.color,
|
color: group.color,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const daysLeft = findHowManyDaysLeft(activeCycle.end_date ?? new Date());
|
const daysLeft = findHowManyDaysLeft(activeCycle.end_date) ?? 0;
|
||||||
|
|
||||||
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">
|
||||||
|
@ -137,7 +137,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date ?? new Date());
|
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -140,7 +140,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
|
|
||||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||||
|
|
||||||
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date ?? new Date());
|
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { KeyedMutator } from "swr";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useCycle, useUser } from "hooks/store";
|
import { useCycle, useUser } from "hooks/store";
|
||||||
// services
|
|
||||||
import { CycleService } from "services/cycle.service";
|
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
|
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
|
||||||
import { CycleGanttBlock } from "components/cycles";
|
import { CycleGanttBlock } from "components/cycles";
|
||||||
@ -17,14 +14,10 @@ import { EUserProjectRoles } from "constants/project";
|
|||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
cycleIds: string[];
|
cycleIds: string[];
|
||||||
mutateCycles?: KeyedMutator<ICycle[]>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// services
|
|
||||||
const cycleService = new CycleService();
|
|
||||||
|
|
||||||
export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
||||||
const { cycleIds, mutateCycles } = props;
|
const { cycleIds } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -32,38 +25,15 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { getCycleById } = useCycle();
|
const { getCycleById, updateCycleDetails } = useCycle();
|
||||||
|
|
||||||
const handleCycleUpdate = (cycle: ICycle, payload: IBlockUpdateData) => {
|
const handleCycleUpdate = async (cycle: ICycle, data: IBlockUpdateData) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug || !cycle) return;
|
||||||
mutateCycles &&
|
|
||||||
mutateCycles((prevData: any) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
const newList = prevData.map((p: any) => ({
|
const payload: any = { ...data };
|
||||||
...p,
|
if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
|
||||||
...(p.id === cycle.id
|
|
||||||
? {
|
|
||||||
start_date: payload.start_date ? payload.start_date : p.start_date,
|
|
||||||
target_date: payload.target_date ? payload.target_date : p.end_date,
|
|
||||||
sort_order: payload.sort_order ? payload.sort_order.newSortOrder : p.sort_order,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (payload.sort_order) {
|
await updateCycleDetails(workspaceSlug.toString(), cycle.project, cycle.id, payload);
|
||||||
const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0];
|
|
||||||
newList.splice(payload.sort_order.destinationIndex, 0, removedElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newList;
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
const newPayload: any = { ...payload };
|
|
||||||
|
|
||||||
if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;
|
|
||||||
|
|
||||||
cycleService.patchCycle(workspaceSlug.toString(), cycle.project, cycle.id, newPayload);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: (ICycle | null)[]) => {
|
const blockFormat = (blocks: (ICycle | null)[]) => {
|
||||||
|
@ -318,6 +318,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const issueCount =
|
const issueCount =
|
||||||
cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||||
|
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date);
|
||||||
|
|
||||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
|
|
||||||
@ -375,8 +376,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
backgroundColor: `${currentCycle.color}20`,
|
backgroundColor: `${currentCycle.color}20`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentCycle.value === "current"
|
{currentCycle.value === "current" && daysLeft !== undefined
|
||||||
? `${findHowManyDaysLeft(cycleDetails.end_date ?? new Date())} ${currentCycle.label}`
|
? `${daysLeft} ${currentCycle.label}`
|
||||||
: `${currentCycle.label}`}
|
: `${currentCycle.label}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -83,7 +83,7 @@ export const AssignedOverdueIssueListItem: React.FC<IssueListItemProps> = observ
|
|||||||
const blockedByIssueProjectDetails =
|
const blockedByIssueProjectDetails =
|
||||||
blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null;
|
blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null;
|
||||||
|
|
||||||
const dueBy = findTotalDaysInRange(new Date(issueDetails.target_date ?? ""), new Date(), false);
|
const dueBy = findTotalDaysInRange(new Date(issueDetails.target_date ?? ""), new Date(), false) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlLink
|
<ControlLink
|
||||||
@ -212,7 +212,7 @@ export const CreatedOverdueIssueListItem: React.FC<IssueListItemProps> = observe
|
|||||||
|
|
||||||
const projectDetails = getProjectById(issue.project_id);
|
const projectDetails = getProjectById(issue.project_id);
|
||||||
|
|
||||||
const dueBy = findTotalDaysInRange(new Date(issue.target_date ?? ""), new Date(), false);
|
const dueBy = findTotalDaysInRange(new Date(issue.target_date ?? ""), new Date(), false) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlLink
|
<ControlLink
|
||||||
|
@ -3,8 +3,7 @@ import { TIssue } from "@plane/types";
|
|||||||
import { IGanttBlock } from "components/gantt-chart";
|
import { IGanttBlock } from "components/gantt-chart";
|
||||||
|
|
||||||
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
||||||
blocks &&
|
blocks?.map((block) => ({
|
||||||
blocks.map((block) => ({
|
|
||||||
data: block,
|
data: block,
|
||||||
id: block.id,
|
id: block.id,
|
||||||
sort_order: block.sort_order,
|
sort_order: block.sort_order,
|
||||||
|
@ -93,7 +93,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
{blocks ? (
|
{blocks ? (
|
||||||
blocks.map((block, index) => {
|
blocks.map((block, index) => {
|
||||||
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
|
const duration = findTotalDaysInRange(block.start_date, block.target_date);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
@ -130,9 +130,11 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
<CycleGanttSidebarBlock data={block.data} />
|
<CycleGanttSidebarBlock data={block.data} />
|
||||||
</div>
|
</div>
|
||||||
|
{duration !== undefined && (
|
||||||
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
||||||
{duration} day{duration > 1 ? "s" : ""}
|
{duration} day{duration > 1 ? "s" : ""}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,7 +93,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
{blocks ? (
|
{blocks ? (
|
||||||
blocks.map((block, index) => {
|
blocks.map((block, index) => {
|
||||||
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
|
const duration = findTotalDaysInRange(block.start_date, block.target_date);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
@ -130,9 +130,11 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
<ModuleGanttSidebarBlock data={block.data} />
|
<ModuleGanttSidebarBlock data={block.data} />
|
||||||
</div>
|
</div>
|
||||||
|
{duration !== undefined && (
|
||||||
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
||||||
{duration} day{duration > 1 ? "s" : ""}
|
{duration} day{duration > 1 ? "s" : ""}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,7 +94,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
{blocks ? (
|
{blocks ? (
|
||||||
blocks.map((block, index) => {
|
blocks.map((block, index) => {
|
||||||
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
|
const duration = findTotalDaysInRange(block.start_date, block.target_date);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
@ -131,9 +131,11 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
<IssueGanttSidebarBlock data={block.data} />
|
<IssueGanttSidebarBlock data={block.data} />
|
||||||
</div>
|
</div>
|
||||||
|
{duration !== undefined && (
|
||||||
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
||||||
{duration} day{duration > 1 ? "s" : ""}
|
{duration} day{duration > 1 ? "s" : ""}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,10 +119,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||||
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
|
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
|
||||||
|
|
||||||
const duration =
|
const duration = findTotalDaysInRange(block.start_date, block.target_date);
|
||||||
!block.start_date || !block.target_date
|
|
||||||
? null
|
|
||||||
: findTotalDaysInRange(block.start_date, block.target_date);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
@ -166,13 +163,13 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
<IssueGanttSidebarBlock data={block.data} />
|
<IssueGanttSidebarBlock data={block.data} />
|
||||||
</div>
|
</div>
|
||||||
|
{duration !== undefined && (
|
||||||
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
||||||
{duration && (
|
|
||||||
<span>
|
<span>
|
||||||
{duration} day{duration > 1 ? "s" : ""}
|
{duration} day{duration > 1 ? "s" : ""}
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export * from "./attachment";
|
export * from "./attachment";
|
||||||
export * from "./issue-modal";
|
export * from "./issue-modal";
|
||||||
export * from "./view-select";
|
|
||||||
export * from "./delete-issue-modal";
|
export * from "./delete-issue-modal";
|
||||||
export * from "./description-form";
|
export * from "./description-form";
|
||||||
export * from "./issue-layouts";
|
export * from "./issue-layouts";
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
// ui
|
|
||||||
import { CustomDatePicker } from "components/ui";
|
|
||||||
import { Tooltip } from "@plane/ui";
|
|
||||||
import { CalendarCheck } from "lucide-react";
|
|
||||||
// helpers
|
|
||||||
import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper";
|
|
||||||
// types
|
|
||||||
import { TIssue } from "@plane/types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
issue: TIssue;
|
|
||||||
onChange: (date: string | null) => void;
|
|
||||||
handleOnOpen?: () => void;
|
|
||||||
handleOnClose?: () => void;
|
|
||||||
tooltipPosition?: "top" | "bottom";
|
|
||||||
className?: string;
|
|
||||||
noBorder?: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewDueDateSelect: React.FC<Props> = ({
|
|
||||||
issue,
|
|
||||||
onChange,
|
|
||||||
handleOnOpen,
|
|
||||||
handleOnClose,
|
|
||||||
tooltipPosition = "top",
|
|
||||||
className = "",
|
|
||||||
noBorder = false,
|
|
||||||
disabled,
|
|
||||||
}) => {
|
|
||||||
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
|
||||||
minDate?.setDate(minDate.getDate());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
tooltipHeading="Due date"
|
|
||||||
tooltipContent={issue.target_date ? renderFormattedDate(issue.target_date) ?? "N/A" : "N/A"}
|
|
||||||
position={tooltipPosition}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`group max-w-[6.5rem] flex-shrink-0 ${className} ${
|
|
||||||
issue.target_date === null
|
|
||||||
? ""
|
|
||||||
: issue.target_date < new Date().toISOString()
|
|
||||||
? "text-red-600"
|
|
||||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<CustomDatePicker
|
|
||||||
value={issue?.target_date}
|
|
||||||
onChange={onChange}
|
|
||||||
className={`bg-transparent ${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"}`}
|
|
||||||
customInput={
|
|
||||||
<div
|
|
||||||
className={`flex items-center justify-center gap-2 rounded border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-200 hover:bg-custom-background-80 ${
|
|
||||||
issue.target_date ? "pr-6 text-custom-text-300" : "text-custom-text-400"
|
|
||||||
} ${disabled ? "cursor-not-allowed" : "cursor-pointer"}`}
|
|
||||||
>
|
|
||||||
{issue.target_date ? (
|
|
||||||
<>
|
|
||||||
<CalendarCheck className="h-3.5 w-3.5 flex-shrink-0" />
|
|
||||||
<span>{renderFormattedDate(issue.target_date) ?? "_ _"}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CalendarCheck className="h-3.5 w-3.5 flex-shrink-0" />
|
|
||||||
<span>Due Date</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
minDate={minDate ?? undefined}
|
|
||||||
noBorder={noBorder}
|
|
||||||
handleOnOpen={handleOnOpen}
|
|
||||||
handleOnClose={handleOnClose}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,64 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { Triangle } from "lucide-react";
|
|
||||||
import sortBy from "lodash/sortBy";
|
|
||||||
// store hooks
|
|
||||||
import { useEstimate } from "hooks/store";
|
|
||||||
// ui
|
|
||||||
import { CustomSelect, Tooltip } from "@plane/ui";
|
|
||||||
// types
|
|
||||||
import { TIssue } from "@plane/types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
issue: TIssue;
|
|
||||||
onChange: (data: number) => void;
|
|
||||||
tooltipPosition?: "top" | "bottom";
|
|
||||||
customButton?: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewEstimateSelect: React.FC<Props> = observer((props) => {
|
|
||||||
const { issue, onChange, tooltipPosition = "top", customButton = false, disabled } = props;
|
|
||||||
const { areEstimatesEnabledForCurrentProject, activeEstimateDetails, getEstimatePointValue } = useEstimate();
|
|
||||||
|
|
||||||
const estimateValue = getEstimatePointValue(issue.estimate_point, issue.project_id);
|
|
||||||
|
|
||||||
const estimateLabels = (
|
|
||||||
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue} position={tooltipPosition}>
|
|
||||||
<div className="flex items-center gap-1 text-custom-text-200">
|
|
||||||
<Triangle className="h-3 w-3" />
|
|
||||||
{estimateValue ?? "None"}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!areEstimatesEnabledForCurrentProject) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomSelect
|
|
||||||
value={issue.estimate_point}
|
|
||||||
onChange={onChange}
|
|
||||||
{...(customButton ? { customButton: estimateLabels } : { label: estimateLabels })}
|
|
||||||
maxHeight="md"
|
|
||||||
noChevron
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
<CustomSelect.Option value={null}>
|
|
||||||
<>
|
|
||||||
<span>
|
|
||||||
<Triangle className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
None
|
|
||||||
</>
|
|
||||||
</CustomSelect.Option>
|
|
||||||
{sortBy(activeEstimateDetails?.points, "key")?.map((estimate) => (
|
|
||||||
<CustomSelect.Option key={estimate.id} value={estimate.key}>
|
|
||||||
<>
|
|
||||||
<Triangle className="h-3 w-3" />
|
|
||||||
{estimate.value}
|
|
||||||
</>
|
|
||||||
</CustomSelect.Option>
|
|
||||||
))}
|
|
||||||
</CustomSelect>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from "./due-date";
|
|
||||||
export * from "./estimate";
|
|
||||||
export * from "./start-date";
|
|
@ -1,72 +0,0 @@
|
|||||||
// ui
|
|
||||||
import { CustomDatePicker } from "components/ui";
|
|
||||||
import { Tooltip } from "@plane/ui";
|
|
||||||
import { CalendarClock } from "lucide-react";
|
|
||||||
// helpers
|
|
||||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
|
||||||
// types
|
|
||||||
import { TIssue } from "@plane/types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
issue: TIssue;
|
|
||||||
onChange: (date: string | null) => void;
|
|
||||||
handleOnOpen?: () => void;
|
|
||||||
handleOnClose?: () => void;
|
|
||||||
tooltipPosition?: "top" | "bottom";
|
|
||||||
className?: string;
|
|
||||||
noBorder?: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewStartDateSelect: React.FC<Props> = ({
|
|
||||||
issue,
|
|
||||||
onChange,
|
|
||||||
handleOnOpen,
|
|
||||||
handleOnClose,
|
|
||||||
tooltipPosition = "top",
|
|
||||||
className = "",
|
|
||||||
noBorder = false,
|
|
||||||
disabled,
|
|
||||||
}) => {
|
|
||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
|
||||||
maxDate?.setDate(maxDate.getDate());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
tooltipHeading="Start date"
|
|
||||||
tooltipContent={issue.start_date ? renderFormattedDate(issue.start_date) ?? "N/A" : "N/A"}
|
|
||||||
position={tooltipPosition}
|
|
||||||
>
|
|
||||||
<div className={`group max-w-[6.5rem] flex-shrink-0 ${className}`}>
|
|
||||||
<CustomDatePicker
|
|
||||||
value={issue?.start_date}
|
|
||||||
onChange={onChange}
|
|
||||||
maxDate={maxDate ?? undefined}
|
|
||||||
noBorder={noBorder}
|
|
||||||
handleOnOpen={handleOnOpen}
|
|
||||||
customInput={
|
|
||||||
<div
|
|
||||||
className={`flex items-center justify-center gap-2 rounded border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-200 hover:bg-custom-background-80 ${
|
|
||||||
issue?.start_date ? "pr-6 text-custom-text-300" : "text-custom-text-400"
|
|
||||||
} ${disabled ? "cursor-not-allowed" : "cursor-pointer"}`}
|
|
||||||
>
|
|
||||||
{issue?.start_date ? (
|
|
||||||
<>
|
|
||||||
<CalendarClock className="h-3.5 w-3.5 flex-shrink-0" />
|
|
||||||
<span>{renderFormattedDate(issue?.start_date ?? "_ _")}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CalendarClock className="h-3.5 w-3.5 flex-shrink-0" />
|
|
||||||
<span>Start Date</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
handleOnClose={handleOnClose}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
@ -13,37 +13,32 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
const { projectModuleIds, moduleMap } = useModule();
|
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
|
const { projectModuleIds, moduleMap, updateModuleDetails } = useModule();
|
||||||
|
|
||||||
const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => {
|
const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug || !module) return;
|
||||||
// FIXME
|
|
||||||
//updateModuleGanttStructure(workspaceSlug.toString(), module.project, module, payload);
|
const payload: any = { ...data };
|
||||||
|
if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
|
||||||
|
|
||||||
|
await updateModuleDetails(workspaceSlug.toString(), module.project, module.id, payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: string[]) =>
|
const blockFormat = (blocks: string[]) =>
|
||||||
blocks && blocks.length > 0
|
blocks?.map((blockId) => {
|
||||||
? blocks
|
|
||||||
.filter((blockId) => {
|
|
||||||
const block = moduleMap[blockId];
|
|
||||||
return block.start_date && block.target_date && new Date(block.start_date) <= new Date(block.target_date);
|
|
||||||
})
|
|
||||||
.map((blockId) => {
|
|
||||||
const block = moduleMap[blockId];
|
const block = moduleMap[blockId];
|
||||||
return {
|
return {
|
||||||
data: block,
|
data: block,
|
||||||
id: block.id,
|
id: block.id,
|
||||||
sort_order: block.sort_order,
|
sort_order: block.sort_order,
|
||||||
start_date: new Date(block.start_date ?? ""),
|
start_date: block.start_date ? new Date(block.start_date) : null,
|
||||||
target_date: new Date(block.target_date ?? ""),
|
target_date: block.target_date ? new Date(block.target_date) : null,
|
||||||
};
|
};
|
||||||
})
|
});
|
||||||
: [];
|
|
||||||
|
|
||||||
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
|
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
|
||||||
|
|
||||||
const modules = projectModuleIds;
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full overflow-y-auto">
|
<div className="h-full w-full overflow-y-auto">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
@ -57,6 +52,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
|
|||||||
enableBlockRightResize={isAllowed}
|
enableBlockRightResize={isAllowed}
|
||||||
enableBlockMove={isAllowed}
|
enableBlockMove={isAllowed}
|
||||||
enableReorder={isAllowed}
|
enableReorder={isAllowed}
|
||||||
|
showAllBlocks
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -87,11 +87,11 @@ export const renderFormattedTime = (date: string | Date, timeFormat: "12-hour" |
|
|||||||
* @example checkIfStringIsDate("2021-01-01", "2021-01-08") // 8
|
* @example checkIfStringIsDate("2021-01-01", "2021-01-08") // 8
|
||||||
*/
|
*/
|
||||||
export const findTotalDaysInRange = (
|
export const findTotalDaysInRange = (
|
||||||
startDate: Date | string,
|
startDate: Date | string | undefined | null,
|
||||||
endDate: Date | string,
|
endDate: Date | string | undefined | null,
|
||||||
inclusive: boolean = true
|
inclusive: boolean = true
|
||||||
): number => {
|
): number | undefined => {
|
||||||
if (!startDate || !endDate) return 0;
|
if (!startDate || !endDate) return undefined;
|
||||||
// Parse the dates to check if they are valid
|
// Parse the dates to check if they are valid
|
||||||
const parsedStartDate = new Date(startDate);
|
const parsedStartDate = new Date(startDate);
|
||||||
const parsedEndDate = new Date(endDate);
|
const parsedEndDate = new Date(endDate);
|
||||||
@ -110,8 +110,11 @@ export const findTotalDaysInRange = (
|
|||||||
* @param {boolean} inclusive (optional) // default true
|
* @param {boolean} inclusive (optional) // default true
|
||||||
* @example findHowManyDaysLeft("2024-01-01") // 3
|
* @example findHowManyDaysLeft("2024-01-01") // 3
|
||||||
*/
|
*/
|
||||||
export const findHowManyDaysLeft = (date: string | Date, inclusive: boolean = true): number => {
|
export const findHowManyDaysLeft = (
|
||||||
if (!date) return 0;
|
date: Date | string | undefined | null,
|
||||||
|
inclusive: boolean = true
|
||||||
|
): number | undefined => {
|
||||||
|
if (!date) return undefined;
|
||||||
// Pass the date to findTotalDaysInRange function to find the total number of days in range from today
|
// Pass the date to findTotalDaysInRange function to find the total number of days in range from today
|
||||||
return findTotalDaysInRange(new Date(), date, inclusive);
|
return findTotalDaysInRange(new Date(), date, inclusive);
|
||||||
};
|
};
|
||||||
|
@ -103,7 +103,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
const projectId = this.rootStore.app.router.projectId;
|
const projectId = this.rootStore.app.router.projectId;
|
||||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||||
let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project === projectId);
|
let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project === projectId);
|
||||||
allCycles = sortBy(allCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
allCycles = sortBy(allCycles, [(c) => c.sort_order]);
|
||||||
const allCycleIds = allCycles.map((c) => c.id);
|
const allCycleIds = allCycles.map((c) => c.id);
|
||||||
return allCycleIds;
|
return allCycleIds;
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
|
const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
|
||||||
return c.project === projectId && hasEndDatePassed;
|
return c.project === projectId && hasEndDatePassed;
|
||||||
});
|
});
|
||||||
completedCycles = sortBy(completedCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
completedCycles = sortBy(completedCycles, [(c) => c.sort_order]);
|
||||||
const completedCycleIds = completedCycles.map((c) => c.id);
|
const completedCycleIds = completedCycles.map((c) => c.id);
|
||||||
return completedCycleIds;
|
return completedCycleIds;
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
const isStartDateUpcoming = isFuture(new Date(c.start_date ?? ""));
|
const isStartDateUpcoming = isFuture(new Date(c.start_date ?? ""));
|
||||||
return c.project === projectId && isStartDateUpcoming;
|
return c.project === projectId && isStartDateUpcoming;
|
||||||
});
|
});
|
||||||
upcomingCycles = sortBy(upcomingCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
upcomingCycles = sortBy(upcomingCycles, [(c) => c.sort_order]);
|
||||||
const upcomingCycleIds = upcomingCycles.map((c) => c.id);
|
const upcomingCycleIds = upcomingCycles.map((c) => c.id);
|
||||||
return upcomingCycleIds;
|
return upcomingCycleIds;
|
||||||
}
|
}
|
||||||
@ -148,7 +148,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
|
const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
|
||||||
return c.project === projectId && !hasEndDatePassed;
|
return c.project === projectId && !hasEndDatePassed;
|
||||||
});
|
});
|
||||||
incompleteCycles = sortBy(incompleteCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
incompleteCycles = sortBy(incompleteCycles, [(c) => c.sort_order]);
|
||||||
const incompleteCycleIds = incompleteCycles.map((c) => c.id);
|
const incompleteCycleIds = incompleteCycles.map((c) => c.id);
|
||||||
return incompleteCycleIds;
|
return incompleteCycleIds;
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
let draftCycles = Object.values(this.cycleMap ?? {}).filter(
|
let draftCycles = Object.values(this.cycleMap ?? {}).filter(
|
||||||
(c) => c.project === projectId && !c.start_date && !c.end_date
|
(c) => c.project === projectId && !c.start_date && !c.end_date
|
||||||
);
|
);
|
||||||
draftCycles = sortBy(draftCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
draftCycles = sortBy(draftCycles, [(c) => c.sort_order]);
|
||||||
const draftCycleIds = draftCycles.map((c) => c.id);
|
const draftCycleIds = draftCycles.map((c) => c.id);
|
||||||
return draftCycleIds;
|
return draftCycleIds;
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
if (!this.fetchedMap[projectId]) return null;
|
if (!this.fetchedMap[projectId]) return null;
|
||||||
|
|
||||||
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project === projectId);
|
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project === projectId);
|
||||||
cycles = sortBy(cycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
cycles = sortBy(cycles, [(c) => c.sort_order]);
|
||||||
const cycleIds = cycles.map((c) => c.id);
|
const cycleIds = cycles.map((c) => c.id);
|
||||||
return cycleIds || null;
|
return cycleIds || null;
|
||||||
});
|
});
|
||||||
|
@ -100,7 +100,7 @@ export class ModulesStore implements IModuleStore {
|
|||||||
const projectId = this.rootStore.app.router.projectId;
|
const projectId = this.rootStore.app.router.projectId;
|
||||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||||
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId);
|
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId);
|
||||||
projectModules = sortBy(projectModules, [(m) => !m.is_favorite, (m) => m.name.toLowerCase()]);
|
projectModules = sortBy(projectModules, [(m) => m.sort_order]);
|
||||||
const projectModuleIds = projectModules.map((m) => m.id);
|
const projectModuleIds = projectModules.map((m) => m.id);
|
||||||
return projectModuleIds || null;
|
return projectModuleIds || null;
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ export class ModulesStore implements IModuleStore {
|
|||||||
if (!this.fetchedMap[projectId]) return null;
|
if (!this.fetchedMap[projectId]) return null;
|
||||||
|
|
||||||
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId);
|
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId);
|
||||||
projectModules = sortBy(projectModules, [(m) => !m.is_favorite, (m) => m.name.toLowerCase()]);
|
projectModules = sortBy(projectModules, [(m) => m.sort_order]);
|
||||||
const projectModuleIds = projectModules.map((m) => m.id);
|
const projectModuleIds = projectModules.map((m) => m.id);
|
||||||
return projectModuleIds;
|
return projectModuleIds;
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user