diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index 854550b0b..eb12250e0 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -1,7 +1,6 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import { differenceInCalendarDays } from "date-fns"; import { LinkIcon, Signal, @@ -15,7 +14,7 @@ import { CalendarDays, } from "lucide-react"; // hooks -import { useEstimate, useIssueDetail, useProject, useUser } from "hooks/store"; +import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components import { @@ -41,6 +40,7 @@ import { ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, UserGroupIcon } import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; import { cn } from "helpers/common.helper"; +import { shouldHighlightIssueDueDate } from "helpers/issue.helper"; // types import type { TIssueOperations } from "./root"; @@ -65,6 +65,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const { issue: { getIssueById }, } = useIssueDetail(); + const { getStateById } = useProjectState(); // states const [deleteIssueModal, setDeleteIssueModal] = useState(false); @@ -83,6 +84,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { }; 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; minDate?.setDate(minDate.getDate()); @@ -90,8 +92,6 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const maxDate = issue.target_date ? new Date(issue.target_date) : null; maxDate?.setDate(maxDate.getDate()); - const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1; - return ( <> {workspaceSlug && projectId && issue && ( @@ -242,7 +242,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { buttonContainerClassName="w-full text-left" buttonClassName={cn("text-sm", { "text-custom-text-400": !issue.target_date, - "text-red-500": targetDateDistance <= 0, + "text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group), })} hideIcon clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100" diff --git a/web/components/issues/issue-layouts/properties/all-properties.tsx b/web/components/issues/issue-layouts/properties/all-properties.tsx index 7ef9aace8..7c8f638ff 100644 --- a/web/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/components/issues/issue-layouts/properties/all-properties.tsx @@ -1,11 +1,10 @@ import { useCallback, useMemo } from "react"; import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; -import { differenceInCalendarDays } from "date-fns"; import { Layers, Link, Paperclip } from "lucide-react"; import xor from "lodash/xor"; // hooks -import { useEventTracker, useEstimate, useLabel, useIssues } from "hooks/store"; +import { useEventTracker, useEstimate, useLabel, useIssues, useProjectState } from "hooks/store"; // components import { IssuePropertyLabels } from "../properties/labels"; import { Tooltip } from "@plane/ui"; @@ -21,6 +20,7 @@ import { } from "components/dropdowns"; // helpers import { renderFormattedPayloadDate } from "helpers/date-time.helper"; +import { shouldHighlightIssueDueDate } from "helpers/issue.helper"; import { cn } from "helpers/common.helper"; // types import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types"; @@ -48,11 +48,14 @@ export const IssueProperties: React.FC = observer((props) => { const { issues: { addIssueToCycle, removeIssueFromCycle }, } = useIssues(EIssuesStoreType.CYCLE); + const { areEstimatesEnabledForCurrentProject } = useEstimate(); + const { getStateById } = useProjectState(); // router const router = useRouter(); const { workspaceSlug, cycleId, moduleId } = router.query; - const { areEstimatesEnabledForCurrentProject } = useEstimate(); const currentLayout = `${activeLayout} layout`; + // derived values + const stateDetails = getStateById(issue.state_id); const issueOperations = useMemo( () => ({ @@ -232,8 +235,6 @@ export const IssueProperties: React.FC = observer((props) => { const maxDate = issue.target_date ? new Date(issue.target_date) : null; maxDate?.setDate(maxDate.getDate()); - const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1; - return (
{/* basic properties */} @@ -301,7 +302,7 @@ export const IssueProperties: React.FC = observer((props) => { minDate={minDate ?? undefined} placeholder="Due date" 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" disabled={isReadOnly} showTooltip diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx index e07500c03..ebed73b76 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx @@ -1,13 +1,15 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import differenceInCalendarDays from "date-fns/differenceInCalendarDays"; +// hooks +import { useProjectState } from "hooks/store"; // components import { DateDropdown } from "components/dropdowns"; // helpers import { renderFormattedPayloadDate } from "helpers/date-time.helper"; +import { shouldHighlightIssueDueDate } from "helpers/issue.helper"; +import { cn } from "helpers/common.helper"; // types import { TIssue } from "@plane/types"; -import { cn } from "helpers/common.helper"; type Props = { issue: TIssue; @@ -18,8 +20,10 @@ type Props = { export const SpreadsheetDueDateColumn: React.FC = observer((props: Props) => { const { issue, onChange, disabled, onClose } = props; - - const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1; + // store hooks + const { getStateById } = useProjectState(); + // derived values + const stateDetails = getStateById(issue.state_id); return (
@@ -42,7 +46,7 @@ export const SpreadsheetDueDateColumn: React.FC = observer((props: Props) buttonVariant="transparent-with-text" buttonContainerClassName="w-full" 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" onClose={onClose} diff --git a/web/components/issues/peek-overview/properties.tsx b/web/components/issues/peek-overview/properties.tsx index c669c0349..2b428a57b 100644 --- a/web/components/issues/peek-overview/properties.tsx +++ b/web/components/issues/peek-overview/properties.tsx @@ -1,9 +1,8 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { differenceInCalendarDays } from "date-fns"; import { Signal, Tag, Triangle, LayoutPanelTop, CircleDot, CopyPlus, XCircle, CalendarDays } from "lucide-react"; // hooks -import { useIssueDetail, useProject } from "hooks/store"; +import { useIssueDetail, useProject, useProjectState } from "hooks/store"; // ui icons import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon, RelatedIcon } from "@plane/ui"; import { @@ -26,6 +25,7 @@ import { import { renderFormattedPayloadDate } from "helpers/date-time.helper"; // helpers import { cn } from "helpers/common.helper"; +import { shouldHighlightIssueDueDate } from "helpers/issue.helper"; interface IPeekOverviewProperties { workspaceSlug: string; @@ -42,11 +42,13 @@ export const PeekOverviewProperties: FC = observer((pro const { issue: { getIssueById }, } = useIssueDetail(); + const { getStateById } = useProjectState(); // derived values const issue = getIssueById(issueId); if (!issue) return <>; const projectDetails = getProjectById(issue.project_id); const isEstimateEnabled = projectDetails?.estimate; + const stateDetails = getStateById(issue.state_id); const minDate = issue.start_date ? new Date(issue.start_date) : null; minDate?.setDate(minDate.getDate()); @@ -54,8 +56,6 @@ export const PeekOverviewProperties: FC = observer((pro const maxDate = issue.target_date ? new Date(issue.target_date) : null; maxDate?.setDate(maxDate.getDate()); - const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1; - return (
Properties
@@ -169,7 +169,7 @@ export const PeekOverviewProperties: FC = observer((pro buttonContainerClassName="w-full text-left" buttonClassName={cn("text-sm", { "text-custom-text-400": !issue.target_date, - "text-red-500": targetDateDistance <= 0, + "text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group), })} hideIcon clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100" diff --git a/web/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index 789b624e7..831cb321e 100644 --- a/web/helpers/issue.helper.ts +++ b/web/helpers/issue.helper.ts @@ -1,11 +1,20 @@ import { v4 as uuidv4 } from "uuid"; +import differenceInCalendarDays from "date-fns/differenceInCalendarDays"; // helpers import { orderArrayBy } from "helpers/array.helper"; // 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"; // constants import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; +import { STATE_GROUPS } from "constants/state"; type THandleIssuesMutation = ( formData: Partial, @@ -134,6 +143,26 @@ export const createIssuePayload: (projectId: string, formData: Partial) 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[] => blocks?.map((block) => ({ data: block,