From 4c16ed8b2348da02e3c888b66ea7bcf139632853 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 17 May 2024 12:45:28 +0530 Subject: [PATCH] [WEB-1336] fix: issue dates conflict in the calendar layout (#4480) * fix: calendar dnd for due dates before issue start date * chore: start date in calender view * fix: add existing issues to calendar layout --------- Co-authored-by: NarayanBavisetti --- apiserver/plane/app/views/search.py | 1 + packages/types/src/project/projects.d.ts | 1 + .../modals/existing-issues-list-modal.tsx | 20 +++++---- .../issue-layouts/calendar/day-tile.tsx | 41 ++++++++++++++----- .../calendar/quick-add-issue-form.tsx | 28 ++++++++----- 5 files changed, 61 insertions(+), 30 deletions(-) diff --git a/apiserver/plane/app/views/search.py b/apiserver/plane/app/views/search.py index 4a4ffd826..93bab2de3 100644 --- a/apiserver/plane/app/views/search.py +++ b/apiserver/plane/app/views/search.py @@ -289,6 +289,7 @@ class IssueSearchEndpoint(BaseAPIView): issues.values( "name", "id", + "start_date", "sequence_id", "project__name", "project__identifier", diff --git a/packages/types/src/project/projects.d.ts b/packages/types/src/project/projects.d.ts index 157ecb16e..459d9f0e2 100644 --- a/packages/types/src/project/projects.d.ts +++ b/packages/types/src/project/projects.d.ts @@ -147,6 +147,7 @@ export interface ISearchIssueResponse { project__identifier: string; project__name: string; sequence_id: number; + start_date: string | null; state__color: string; state__group: TStateGroups; state__name: string; diff --git a/web/components/core/modals/existing-issues-list-modal.tsx b/web/components/core/modals/existing-issues-list-modal.tsx index 93e2c2ea7..63c0b17a0 100644 --- a/web/components/core/modals/existing-issues-list-modal.tsx +++ b/web/components/core/modals/existing-issues-list-modal.tsx @@ -1,17 +1,17 @@ import React, { useEffect, useState } from "react"; import { Rocket, Search, X } from "lucide-react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; +// types import { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types"; -// services +// ui import { Button, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; +// hooks import useDebounce from "@/hooks/use-debounce"; import { usePlatformOS } from "@/hooks/use-platform-os"; +// services import { ProjectService } from "@/services/project"; -// hooks // components import { IssueSearchModalEmptyState } from "./issue-search-modal-empty-state"; -// ui -// types type Props = { workspaceSlug: string | undefined; @@ -21,6 +21,7 @@ type Props = { searchParams: Partial; handleOnSubmit: (data: ISearchIssueResponse[]) => Promise; workspaceLevelToggle?: boolean; + shouldHideIssue?: (issue: ISearchIssueResponse) => boolean; }; const projectService = new ProjectService(); @@ -34,6 +35,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { searchParams, handleOnSubmit, workspaceLevelToggle = false, + shouldHideIssue, } = props; // states const [isLoading, setIsLoading] = useState(false); @@ -87,6 +89,8 @@ export const ExistingIssuesListModal: React.FC = (props) => { }); }, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, workspaceSlug]); + const filteredIssues = issues.filter((issue) => !shouldHideIssue?.(issue)); + return ( <> setSearchTerm("")} appear> @@ -207,16 +211,16 @@ export const ExistingIssuesListModal: React.FC = (props) => { ) : ( <> - {issues.length === 0 ? ( + {filteredIssues.length === 0 ? ( ) : ( -
    0 ? "p-2" : ""}`}> - {issues.map((issue) => { +
      0 ? "p-2" : ""}`}> + {filteredIssues.map((issue) => { const selected = selectedIssues.some((i) => i.id === issue.id); return ( diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index 7dfcfdcac..d9461e1f6 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -1,9 +1,12 @@ import { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { differenceInCalendarDays } from "date-fns"; import { observer } from "mobx-react-lite"; // types import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { CalendarIssueBlocks, ICalendarDate } from "@/components/issues"; import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils"; @@ -91,6 +94,23 @@ export const CalendarDayTile: React.FC = observer((props) => { setIsDraggingOver(false); const sourceData = source?.data as { id: string; date: string } | undefined; const destinationData = self?.data as { date: string } | undefined; + if (!sourceData || !destinationData) return; + + const issueDetails = issues?.[sourceData?.id]; + if (issueDetails?.start_date) { + const issueStartDate = new Date(issueDetails.start_date); + const targetDate = new Date(destinationData?.date); + const diffInDays = differenceInCalendarDays(targetDate, issueStartDate); + if (diffInDays < 0) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Due date cannot be before the start date of the issue.", + }); + return; + } + } + handleDragAndDrop(sourceData?.id, sourceData?.date, destinationData?.date); setShowAllIssues(true); highlightIssueOnDrop(source?.element?.id, false); @@ -107,7 +127,7 @@ export const CalendarDayTile: React.FC = observer((props) => { const isToday = date.date.toDateString() === new Date().toDateString(); const isSelectedDate = date.date.toDateString() == selectedDate.toDateString(); - const isWeekend = date.date.getDay() === 0 || date.date.getDay() === 6; + const isWeekend = [0, 6].includes(date.date.getDay()); const isMonthLayout = calendarLayout === "month"; const normalBackground = isWeekend ? "bg-custom-background-90" : "bg-custom-background-100"; @@ -124,11 +144,7 @@ export const CalendarDayTile: React.FC = observer((props) => { ? "font-medium" : "text-custom-text-300" : "font-medium" // if week layout, highlight all days - } ${ - date.date.getDay() === 0 || date.date.getDay() === 6 - ? "bg-custom-background-90" - : "bg-custom-background-100" - } `} + } ${isWeekend ? "bg-custom-background-90" : "bg-custom-background-100"} `} > {date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "} {isToday ? ( @@ -143,9 +159,12 @@ export const CalendarDayTile: React.FC = observer((props) => { {/* content */}
      = observer((props) => { )} >
      = observer((props) => { {date.date.getDate()}
      - {totalIssues > 0 &&
      } + {totalIssues > 0 &&
      }
      diff --git a/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx index ea75e7748..7e77a63ab 100644 --- a/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx @@ -1,25 +1,24 @@ import { useEffect, useRef, useState } from "react"; +import { differenceInCalendarDays } from "date-fns"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { useForm } from "react-hook-form"; -// components import { PlusIcon } from "lucide-react"; +// types import { ISearchIssueResponse, TIssue } from "@plane/types"; +// ui import { TOAST_TYPE, setPromiseToast, setToast, CustomMenu } from "@plane/ui"; +// components import { ExistingIssuesListModal } from "@/components/core"; -// hooks +// constants import { ISSUE_CREATED } from "@/constants/event-tracker"; +// helpers import { cn } from "@/helpers/common.helper"; import { createIssuePayload } from "@/helpers/issue.helper"; +// hooks import { useEventTracker, useIssueDetail, useProject } from "@/hooks/store"; import useKeypress from "@/hooks/use-keypress"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; -// helpers -// icons -// ui -// types -// constants -// helper type Props = { formKey: keyof TIssue; @@ -182,9 +181,7 @@ export const CalendarQuickAddIssueForm: React.FC = observer((props) => { updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, prePopulatedData ?? {}) ) ); - if (addIssuesToView) { - await addIssuesToView(issueIds); - } + await addIssuesToView?.(issueIds); } catch (error) { setToast({ type: TOAST_TYPE.ERROR, @@ -212,6 +209,15 @@ export const CalendarQuickAddIssueForm: React.FC = observer((props) => { handleClose={() => setIsExistingIssueModalOpen(false)} searchParams={ExistingIssuesListModalPayload} handleOnSubmit={handleAddIssuesToView} + shouldHideIssue={(issue) => { + if (issue.start_date && prePopulatedData?.target_date) { + const issueStartDate = new Date(issue.start_date); + const targetDate = new Date(prePopulatedData.target_date); + const diffInDays = differenceInCalendarDays(targetDate, issueStartDate); + if (diffInDays < 0) return true; + } + return false; + }} /> )} {isOpen && (