forked from github/plane
fix: due date highlight logic (#3763)
This commit is contained in:
parent
ba6479674c
commit
33c99ded77
@ -1,7 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } 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 { differenceInCalendarDays } from "date-fns";
|
|
||||||
import {
|
import {
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
Signal,
|
Signal,
|
||||||
@ -15,7 +14,7 @@ import {
|
|||||||
CalendarDays,
|
CalendarDays,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEstimate, useIssueDetail, useProject, useUser } from "hooks/store";
|
import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
@ -41,6 +40,7 @@ import { ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, UserGroupIcon }
|
|||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
|
import { shouldHighlightIssueDueDate } from "helpers/issue.helper";
|
||||||
// types
|
// types
|
||||||
import type { TIssueOperations } from "./root";
|
import type { TIssueOperations } from "./root";
|
||||||
|
|
||||||
@ -65,6 +65,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
|
const { getStateById } = useProjectState();
|
||||||
// states
|
// states
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const projectDetails = issue ? getProjectById(issue.project_id) : null;
|
const projectDetails = issue ? getProjectById(issue.project_id) : null;
|
||||||
|
const stateDetails = getStateById(issue.state_id);
|
||||||
|
|
||||||
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
||||||
minDate?.setDate(minDate.getDate());
|
minDate?.setDate(minDate.getDate());
|
||||||
@ -90,8 +92,6 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{workspaceSlug && projectId && issue && (
|
{workspaceSlug && projectId && issue && (
|
||||||
@ -242,7 +242,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
buttonContainerClassName="w-full text-left"
|
buttonContainerClassName="w-full text-left"
|
||||||
buttonClassName={cn("text-sm", {
|
buttonClassName={cn("text-sm", {
|
||||||
"text-custom-text-400": !issue.target_date,
|
"text-custom-text-400": !issue.target_date,
|
||||||
"text-red-500": targetDateDistance <= 0,
|
"text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
|
||||||
})}
|
})}
|
||||||
hideIcon
|
hideIcon
|
||||||
clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100"
|
clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100"
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { differenceInCalendarDays } from "date-fns";
|
|
||||||
import { Layers, Link, Paperclip } from "lucide-react";
|
import { Layers, Link, Paperclip } from "lucide-react";
|
||||||
import xor from "lodash/xor";
|
import xor from "lodash/xor";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useEstimate, useLabel, useIssues } from "hooks/store";
|
import { useEventTracker, useEstimate, useLabel, useIssues, useProjectState } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { IssuePropertyLabels } from "../properties/labels";
|
import { IssuePropertyLabels } from "../properties/labels";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
@ -21,6 +20,7 @@ import {
|
|||||||
} from "components/dropdowns";
|
} from "components/dropdowns";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
|
import { shouldHighlightIssueDueDate } from "helpers/issue.helper";
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types";
|
import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types";
|
||||||
@ -48,11 +48,14 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
issues: { addIssueToCycle, removeIssueFromCycle },
|
issues: { addIssueToCycle, removeIssueFromCycle },
|
||||||
} = useIssues(EIssuesStoreType.CYCLE);
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
|
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
||||||
|
const { getStateById } = useProjectState();
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, cycleId, moduleId } = router.query;
|
const { workspaceSlug, cycleId, moduleId } = router.query;
|
||||||
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
|
||||||
const currentLayout = `${activeLayout} layout`;
|
const currentLayout = `${activeLayout} layout`;
|
||||||
|
// derived values
|
||||||
|
const stateDetails = getStateById(issue.state_id);
|
||||||
|
|
||||||
const issueOperations = useMemo(
|
const issueOperations = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -232,8 +235,6 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{/* basic properties */}
|
{/* basic properties */}
|
||||||
@ -301,7 +302,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
minDate={minDate ?? undefined}
|
minDate={minDate ?? undefined}
|
||||||
placeholder="Due date"
|
placeholder="Due date"
|
||||||
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
|
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
|
||||||
buttonClassName={targetDateDistance <= 0 ? "text-red-500" : ""}
|
buttonClassName={shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group) ? "text-red-500" : ""}
|
||||||
clearIconClassName="!text-custom-text-100"
|
clearIconClassName="!text-custom-text-100"
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
showTooltip
|
showTooltip
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
// hooks
|
||||||
|
import { useProjectState } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { DateDropdown } from "components/dropdowns";
|
import { DateDropdown } from "components/dropdowns";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
|
import { shouldHighlightIssueDueDate } from "helpers/issue.helper";
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
import { cn } from "helpers/common.helper";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: TIssue;
|
issue: TIssue;
|
||||||
@ -18,8 +20,10 @@ type Props = {
|
|||||||
|
|
||||||
export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props) => {
|
export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props) => {
|
||||||
const { issue, onChange, disabled, onClose } = props;
|
const { issue, onChange, disabled, onClose } = props;
|
||||||
|
// store hooks
|
||||||
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
const { getStateById } = useProjectState();
|
||||||
|
// derived values
|
||||||
|
const stateDetails = getStateById(issue.state_id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
||||||
@ -42,7 +46,7 @@ export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props)
|
|||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
buttonClassName={cn("rounded-none text-left", {
|
buttonClassName={cn("rounded-none text-left", {
|
||||||
"text-red-500": targetDateDistance <= 0,
|
"text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
|
||||||
})}
|
})}
|
||||||
clearIconClassName="!text-custom-text-100"
|
clearIconClassName="!text-custom-text-100"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { differenceInCalendarDays } from "date-fns";
|
|
||||||
import { Signal, Tag, Triangle, LayoutPanelTop, CircleDot, CopyPlus, XCircle, CalendarDays } from "lucide-react";
|
import { Signal, Tag, Triangle, LayoutPanelTop, CircleDot, CopyPlus, XCircle, CalendarDays } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail, useProject } from "hooks/store";
|
import { useIssueDetail, useProject, useProjectState } from "hooks/store";
|
||||||
// ui icons
|
// ui icons
|
||||||
import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon, RelatedIcon } from "@plane/ui";
|
import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon, RelatedIcon } from "@plane/ui";
|
||||||
import {
|
import {
|
||||||
@ -26,6 +25,7 @@ import {
|
|||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
|
import { shouldHighlightIssueDueDate } from "helpers/issue.helper";
|
||||||
|
|
||||||
interface IPeekOverviewProperties {
|
interface IPeekOverviewProperties {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -42,11 +42,13 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
|
const { getStateById } = useProjectState();
|
||||||
// derived values
|
// derived values
|
||||||
const issue = getIssueById(issueId);
|
const issue = getIssueById(issueId);
|
||||||
if (!issue) return <></>;
|
if (!issue) return <></>;
|
||||||
const projectDetails = getProjectById(issue.project_id);
|
const projectDetails = getProjectById(issue.project_id);
|
||||||
const isEstimateEnabled = projectDetails?.estimate;
|
const isEstimateEnabled = projectDetails?.estimate;
|
||||||
|
const stateDetails = getStateById(issue.state_id);
|
||||||
|
|
||||||
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
||||||
minDate?.setDate(minDate.getDate());
|
minDate?.setDate(minDate.getDate());
|
||||||
@ -54,8 +56,6 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<h6 className="text-sm font-medium">Properties</h6>
|
<h6 className="text-sm font-medium">Properties</h6>
|
||||||
@ -169,7 +169,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
buttonContainerClassName="w-full text-left"
|
buttonContainerClassName="w-full text-left"
|
||||||
buttonClassName={cn("text-sm", {
|
buttonClassName={cn("text-sm", {
|
||||||
"text-custom-text-400": !issue.target_date,
|
"text-custom-text-400": !issue.target_date,
|
||||||
"text-red-500": targetDateDistance <= 0,
|
"text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
|
||||||
})}
|
})}
|
||||||
hideIcon
|
hideIcon
|
||||||
clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100"
|
clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100"
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
||||||
// helpers
|
// helpers
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue, TIssueGroupByOptions, TIssueLayouts, TIssueOrderByOptions, TIssueParams } from "@plane/types";
|
import {
|
||||||
|
TIssue,
|
||||||
|
TIssueGroupByOptions,
|
||||||
|
TIssueLayouts,
|
||||||
|
TIssueOrderByOptions,
|
||||||
|
TIssueParams,
|
||||||
|
TStateGroups,
|
||||||
|
} from "@plane/types";
|
||||||
import { IGanttBlock } from "components/gantt-chart";
|
import { IGanttBlock } from "components/gantt-chart";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
|
import { STATE_GROUPS } from "constants/state";
|
||||||
|
|
||||||
type THandleIssuesMutation = (
|
type THandleIssuesMutation = (
|
||||||
formData: Partial<TIssue>,
|
formData: Partial<TIssue>,
|
||||||
@ -134,6 +143,26 @@ export const createIssuePayload: (projectId: string, formData: Partial<TIssue>)
|
|||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description check if the issue due date should be highlighted
|
||||||
|
* @param date
|
||||||
|
* @param stateGroup
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
export const shouldHighlightIssueDueDate = (
|
||||||
|
date: string | Date | null,
|
||||||
|
stateGroup: TStateGroups | undefined
|
||||||
|
): boolean => {
|
||||||
|
if (!date || !stateGroup) return false;
|
||||||
|
// if the issue is completed or cancelled, don't highlight the due date
|
||||||
|
if ([STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key].includes(stateGroup)) return false;
|
||||||
|
|
||||||
|
const parsedDate = new Date(date);
|
||||||
|
const targetDateDistance = differenceInCalendarDays(parsedDate, new Date());
|
||||||
|
|
||||||
|
// if the issue is overdue, highlight the due date
|
||||||
|
return targetDateDistance <= 0;
|
||||||
|
};
|
||||||
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
||||||
blocks?.map((block) => ({
|
blocks?.map((block) => ({
|
||||||
data: block,
|
data: block,
|
||||||
|
Loading…
Reference in New Issue
Block a user