fix: due date highlight logic

This commit is contained in:
Aaryan Khandelwal 2024-02-22 18:04:42 +05:30
parent 1836f12a4d
commit f523078689
5 changed files with 57 additions and 22 deletions

View File

@ -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"

View File

@ -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";
// types // types
import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types"; import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types";
// constants // constants
@ -47,11 +47,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(
() => ({ () => ({
@ -231,8 +234,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 */}
@ -300,7 +301,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

View File

@ -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}

View File

@ -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"

View File

@ -1,10 +1,19 @@
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";
// 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>,
@ -131,3 +140,24 @@ 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;
};