forked from github/plane
[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 <narayan3119@gmail.com>
This commit is contained in:
parent
f9de1e790c
commit
4c16ed8b23
@ -289,6 +289,7 @@ class IssueSearchEndpoint(BaseAPIView):
|
||||
issues.values(
|
||||
"name",
|
||||
"id",
|
||||
"start_date",
|
||||
"sequence_id",
|
||||
"project__name",
|
||||
"project__identifier",
|
||||
|
1
packages/types/src/project/projects.d.ts
vendored
1
packages/types/src/project/projects.d.ts
vendored
@ -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;
|
||||
|
@ -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<TProjectIssuesSearchParams>;
|
||||
handleOnSubmit: (data: ISearchIssueResponse[]) => Promise<void>;
|
||||
workspaceLevelToggle?: boolean;
|
||||
shouldHideIssue?: (issue: ISearchIssueResponse) => boolean;
|
||||
};
|
||||
|
||||
const projectService = new ProjectService();
|
||||
@ -34,6 +35,7 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
||||
searchParams,
|
||||
handleOnSubmit,
|
||||
workspaceLevelToggle = false,
|
||||
shouldHideIssue,
|
||||
} = props;
|
||||
// states
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -87,6 +89,8 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
||||
});
|
||||
}, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, workspaceSlug]);
|
||||
|
||||
const filteredIssues = issues.filter((issue) => !shouldHideIssue?.(issue));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setSearchTerm("")} appear>
|
||||
@ -207,16 +211,16 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
||||
</Loader>
|
||||
) : (
|
||||
<>
|
||||
{issues.length === 0 ? (
|
||||
{filteredIssues.length === 0 ? (
|
||||
<IssueSearchModalEmptyState
|
||||
debouncedSearchTerm={debouncedSearchTerm}
|
||||
isSearching={isSearching}
|
||||
issues={issues}
|
||||
issues={filteredIssues}
|
||||
searchTerm={searchTerm}
|
||||
/>
|
||||
) : (
|
||||
<ul className={`text-sm text-custom-text-100 ${issues.length > 0 ? "p-2" : ""}`}>
|
||||
{issues.map((issue) => {
|
||||
<ul className={`text-sm text-custom-text-100 ${filteredIssues.length > 0 ? "p-2" : ""}`}>
|
||||
{filteredIssues.map((issue) => {
|
||||
const selected = selectedIssues.some((i) => i.id === issue.id);
|
||||
|
||||
return (
|
||||
|
@ -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<Props> = 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<Props> = 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<Props> = 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<Props> = observer((props) => {
|
||||
{/* content */}
|
||||
<div className="h-full w-full hidden md:block">
|
||||
<div
|
||||
className={`h-full w-full select-none ${
|
||||
isDraggingOver ? `${draggingOverBackground} opacity-70` : normalBackground
|
||||
} ${isMonthLayout ? "min-h-[5rem]" : ""}`}
|
||||
className={cn(
|
||||
`h-full w-full select-none ${isDraggingOver ? `${draggingOverBackground} opacity-70` : normalBackground}`,
|
||||
{
|
||||
"min-h-[5rem]": isMonthLayout,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<CalendarIssueBlocks
|
||||
date={date.date}
|
||||
@ -177,7 +196,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn("flex h-6 w-6 items-center justify-center rounded-full ", {
|
||||
className={cn("size-6 flex items-center justify-center rounded-full", {
|
||||
"bg-custom-primary-100 text-white": isSelectedDate,
|
||||
"bg-custom-primary-100/10 text-custom-primary-100 ": isToday && !isSelectedDate,
|
||||
})}
|
||||
@ -185,7 +204,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
{date.date.getDate()}
|
||||
</div>
|
||||
|
||||
{totalIssues > 0 && <div className="mt-1 flex h-1.5 w-1.5 flex-shrink-0 rounded bg-custom-primary-100" />}
|
||||
{totalIssues > 0 && <div className="mt-1 size-1.5 flex flex-shrink-0 rounded bg-custom-primary-100" />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -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<Props> = 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<Props> = 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 && (
|
||||
|
Loading…
Reference in New Issue
Block a user