chore: refactored and resolved build issues on the issues and issue detail page (#3340)
* fix: handled undefined issue_id in list layout * dev: issue detail store and optimization * dev: issue filter and list operations * fix: typo on labels update * dev: Handled all issues in the list layout in project issues * dev: handled kanban and auick add issue in swimlanes * chore: fixed peekoverview in kanban * chore: fixed peekoverview in calendar * chore: fixed peekoverview in gantt * chore: updated quick add in the gantt chart * chore: handled issue detail properties and resolved build issues --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
parent
e6b31e2550
commit
4611ec0b83
112 changed files with 3303 additions and 2560 deletions
|
|
@ -4,12 +4,13 @@ import { observer } from "mobx-react-lite";
|
|||
import { Draggable } from "@hello-pangea/dnd";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
// components
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { Tooltip, ControlLink } from "@plane/ui";
|
||||
// hooks
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// ui
|
||||
// types
|
||||
import { TIssue, TIssueMap } from "@plane/types";
|
||||
import { useProject, useProjectState } from "hooks/store";
|
||||
import { useApplication, useIssueDetail, useProject, useProjectState } from "hooks/store";
|
||||
|
||||
type Props = {
|
||||
issues: TIssueMap | undefined;
|
||||
|
|
@ -23,21 +24,23 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
// router
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const {
|
||||
router: { workspaceSlug, projectId },
|
||||
} = useApplication();
|
||||
const { getProjectById } = useProject();
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
// states
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
|
||||
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleIssuePeekOverview = (issue: TIssue) => {
|
||||
const { query } = router;
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id },
|
||||
});
|
||||
};
|
||||
const handleIssuePeekOverview = (issue: TIssue) =>
|
||||
workspaceSlug &&
|
||||
issue &&
|
||||
issue.project_id &&
|
||||
issue.id &&
|
||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
||||
|
||||
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
|
||||
|
||||
|
|
@ -67,45 +70,53 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
>
|
||||
{issue?.tempId !== undefined && (
|
||||
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`group/calendar-block flex h-8 w-full items-center justify-between gap-1.5 rounded border-[0.5px] border-custom-border-100 px-1 py-1.5 shadow-custom-shadow-2xs ${
|
||||
snapshot.isDragging
|
||||
? "bg-custom-background-90 shadow-custom-shadow-rg"
|
||||
: "bg-custom-background-100 hover:bg-custom-background-90"
|
||||
}`}
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
|
||||
target="_blank"
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
|
||||
>
|
||||
<div className="flex h-full items-center gap-1.5">
|
||||
<span
|
||||
className="h-full w-0.5 flex-shrink-0 rounded"
|
||||
style={{
|
||||
backgroundColor: getProjectStates(issue?.project_id).find(
|
||||
(state) => state?.id == issue?.state_id
|
||||
)?.color,
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
<>
|
||||
{issue?.tempId !== undefined && (
|
||||
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`group/calendar-block flex h-8 w-full items-center justify-between gap-1.5 rounded border-[0.5px] border-custom-border-100 px-1 py-1.5 shadow-custom-shadow-2xs ${
|
||||
snapshot.isDragging
|
||||
? "bg-custom-background-90 shadow-custom-shadow-rg"
|
||||
: "bg-custom-background-100 hover:bg-custom-background-90"
|
||||
}`}
|
||||
>
|
||||
<div className="flex h-full items-center gap-1.5">
|
||||
<span
|
||||
className="h-full w-0.5 flex-shrink-0 rounded"
|
||||
style={{
|
||||
backgroundColor: getProjectStates(issue?.project_id).find(
|
||||
(state) => state?.id == issue?.state_id
|
||||
)?.color,
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="truncate text-xs">{issue.name}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
className={`hidden h-5 w-5 group-hover/calendar-block:block ${isMenuActive ? "!block" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{quickActions(issue, customActionButton)}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="truncate text-xs">{issue.name}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
className={`hidden h-5 w-5 group-hover/calendar-block:block ${isMenuActive ? "!block" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{quickActions(issue, customActionButton)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</ControlLink>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
import { useRouter } from "next/router";
|
||||
// ui
|
||||
import { Tooltip, StateGroupIcon } from "@plane/ui";
|
||||
import { Tooltip, StateGroupIcon, ControlLink } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { TIssue } from "@plane/types";
|
||||
import { useProject, useProjectState } from "hooks/store";
|
||||
import { useApplication, useIssueDetail, useProject, useProjectState } from "hooks/store";
|
||||
|
||||
export const IssueGanttBlock = ({ data }: { data: TIssue }) => {
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const {
|
||||
router: { workspaceSlug },
|
||||
} = useApplication();
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
|
||||
const handleIssuePeekOverview = () => {
|
||||
const { query } = router;
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project_id },
|
||||
});
|
||||
};
|
||||
const handleIssuePeekOverview = () =>
|
||||
workspaceSlug &&
|
||||
data &&
|
||||
data.project_id &&
|
||||
data.id &&
|
||||
setPeekIssue({ workspaceSlug, projectId: data.project_id, issueId: data.id });
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -49,34 +50,42 @@ export const IssueGanttBlock = ({ data }: { data: TIssue }) => {
|
|||
|
||||
// rendering issues on gantt sidebar
|
||||
export const IssueGanttSidebarBlock = ({ data }: { data: TIssue }) => {
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { getProjectById } = useProject();
|
||||
const {
|
||||
router: { workspaceSlug },
|
||||
} = useApplication();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
|
||||
const handleIssuePeekOverview = () => {
|
||||
const { query } = router;
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project_id },
|
||||
});
|
||||
};
|
||||
const handleIssuePeekOverview = () =>
|
||||
workspaceSlug &&
|
||||
data &&
|
||||
data.project_id &&
|
||||
data.id &&
|
||||
setPeekIssue({ workspaceSlug, projectId: data.project_id, issueId: data.id });
|
||||
|
||||
const currentStateDetails =
|
||||
getProjectStates(data?.project_id)?.find((state) => state?.id == data?.state_id) || undefined;
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full cursor-pointer items-center gap-2" onClick={handleIssuePeekOverview}>
|
||||
{currentStateDetails != undefined && (
|
||||
<StateGroupIcon stateGroup={currentStateDetails?.group} color={currentStateDetails?.color} />
|
||||
)}
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{getProjectById(data?.project_id)?.identifier} {data?.sequence_id}
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${data.project_id}/issues/${data.id}`}
|
||||
target="_blank"
|
||||
onClick={handleIssuePeekOverview}
|
||||
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
|
||||
>
|
||||
<div className="relative flex h-full w-full cursor-pointer items-center gap-2" onClick={handleIssuePeekOverview}>
|
||||
{currentStateDetails != undefined && (
|
||||
<StateGroupIcon stateGroup={currentStateDetails?.group} color={currentStateDetails?.color} />
|
||||
)}
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{getProjectById(data?.project_id)?.identifier} {data?.sequence_id}
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={data.name}>
|
||||
<span className="flex-grow truncate text-sm font-medium">{data?.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={data.name}>
|
||||
<span className="flex-grow truncate text-sm font-medium">{data?.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</ControlLink>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useState, useRef } from "react";
|
||||
import { useEffect, useState, useRef, FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import { useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
|
|
@ -12,9 +12,38 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
// types
|
||||
import { TIssue } from "@plane/types";
|
||||
import { IProject, TIssue } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
interface IInputProps {
|
||||
formKey: string;
|
||||
register: any;
|
||||
setFocus: any;
|
||||
projectDetail: IProject | null;
|
||||
}
|
||||
const Inputs: FC<IInputProps> = (props) => {
|
||||
const { formKey, register, setFocus, projectDetail } = props;
|
||||
|
||||
useEffect(() => {
|
||||
setFocus(formKey);
|
||||
}, [formKey, setFocus]);
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center gap-3">
|
||||
<div className="text-xs font-medium text-custom-text-400">{projectDetail?.identifier ?? "..."}</div>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder="Issue Title"
|
||||
{...register(formKey, {
|
||||
required: "Issue title is required.",
|
||||
})}
|
||||
className="w-full rounded-md bg-transparent px-2 py-3 text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type IGanttQuickAddIssueForm = {
|
||||
prePopulatedData?: Partial<TIssue>;
|
||||
onSuccess?: (data: TIssue) => Promise<void> | void;
|
||||
quickAddCallback?: (
|
||||
|
|
@ -30,34 +59,25 @@ const defaultValues: Partial<TIssue> = {
|
|||
name: "",
|
||||
};
|
||||
|
||||
const Inputs = (props: any) => {
|
||||
const { register, setFocus } = props;
|
||||
|
||||
useEffect(() => {
|
||||
setFocus("name");
|
||||
}, [setFocus]);
|
||||
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder="Issue Title"
|
||||
{...register("name", {
|
||||
required: "Issue title is required.",
|
||||
})}
|
||||
className="w-full rounded-md bg-transparent px-2 text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
export const GanttQuickAddIssueForm: React.FC<IGanttQuickAddIssueForm> = observer((props) => {
|
||||
const { prePopulatedData, quickAddCallback, viewId } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { currentProjectDetails } = useProject();
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined;
|
||||
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const handleClose = () => setIsOpen(false);
|
||||
|
||||
useKeypress("Escape", handleClose);
|
||||
useOutsideClickDetector(ref, handleClose);
|
||||
|
||||
// form info
|
||||
const {
|
||||
reset,
|
||||
|
|
@ -67,103 +87,67 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||
formState: { errors, isSubmitting },
|
||||
} = useForm<TIssue>({ defaultValues });
|
||||
|
||||
// ref
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
|
||||
// states
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleClose = () => setIsOpen(false);
|
||||
|
||||
// hooks
|
||||
useKeypress("Escape", handleClose);
|
||||
useOutsideClickDetector(ref, handleClose);
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = getWorkspaceBySlug(workspaceSlug?.toString()!);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) reset({ ...defaultValues });
|
||||
}, [isOpen, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!errors) return;
|
||||
|
||||
Object.keys(errors).forEach((key) => {
|
||||
const error = errors[key as keyof TIssue];
|
||||
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: error?.message?.toString() || "Some error occurred. Please try again.",
|
||||
});
|
||||
});
|
||||
}, [errors, setToastAlert]);
|
||||
|
||||
const onSubmitHandler = async (formData: TIssue) => {
|
||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
const targetDate = new Date();
|
||||
targetDate.setDate(targetDate.getDate() + 1);
|
||||
|
||||
const payload = createIssuePayload(projectId.toString(), {
|
||||
...(prePopulatedData ?? {}),
|
||||
...formData,
|
||||
start_date: renderFormattedPayloadDate(new Date()),
|
||||
target_date: renderFormattedPayloadDate(targetDate),
|
||||
});
|
||||
|
||||
try {
|
||||
if (quickAddCallback) {
|
||||
await quickAddCallback(workspaceSlug.toString(), projectId.toString(), payload, viewId);
|
||||
}
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId));
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Issue created successfully.",
|
||||
});
|
||||
} catch (err: any) {
|
||||
Object.keys(err || {}).forEach((key) => {
|
||||
const error = err?.[key];
|
||||
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
||||
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: errorTitle || "Some error occurred. Please try again.",
|
||||
});
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: err?.message || "Some error occurred. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<form
|
||||
ref={ref}
|
||||
className="mr-2.5 flex items-center gap-x-2 rounded border-[0.5px] border-custom-border-100 bg-custom-background-100 px-2 py-3 shadow-custom-shadow-2xs"
|
||||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
>
|
||||
<div className="h-3 w-3 flex-shrink-0 rounded-full border border-custom-border-1000" />
|
||||
<h4 className="text-xs text-custom-text-400">{currentProjectDetails?.identifier ?? "..."}</h4>
|
||||
<Inputs register={register} setFocus={setFocus} />
|
||||
</form>
|
||||
)}
|
||||
|
||||
{isOpen && (
|
||||
<p className="ml-3 mt-3 text-xs italic text-custom-text-200">
|
||||
Press {"'"}Enter{"'"} to add another issue
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!isOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-x-[6px] rounded-md px-2 py-1 text-custom-primary-100"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className={`${errors && errors?.name && errors?.name?.message ? `border border-red-500/20 bg-red-500/10` : ``}`}
|
||||
>
|
||||
{isOpen ? (
|
||||
<div className="shadow-custom-shadow-sm">
|
||||
<form
|
||||
ref={ref}
|
||||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
className="flex w-full items-center gap-x-3 border-[0.5px] border-custom-border-100 bg-custom-background-100 px-3"
|
||||
>
|
||||
<Inputs formKey={"name"} register={register} setFocus={setFocus} projectDetail={projectDetail ?? null} />
|
||||
</form>
|
||||
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another issue`}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex w-full cursor-pointer items-center gap-2 p-3 py-3 text-custom-primary-100"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,17 +15,16 @@ import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
|
|||
//components
|
||||
import { KanBan } from "./default";
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { DeleteIssueModal, IssuePeekOverview } from "components/issues";
|
||||
import { DeleteIssueModal } from "components/issues";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
import { handleDragDrop } from "./utils";
|
||||
import { IssueKanBanViewStore } from "store/issue/issue_kanban_view.store";
|
||||
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IDraftIssues, IDraftIssuesFilter } from "store/issue/draft";
|
||||
import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile";
|
||||
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
import { TCreateModalStoreTypes } from "constants/issue";
|
||||
import { EIssueFilterType, TCreateModalStoreTypes } from "constants/issue";
|
||||
|
||||
export interface IBaseKanBanLayout {
|
||||
issues: IProjectIssues | ICycleIssues | IDraftIssues | IModuleIssues | IProjectViewIssues | IProfileIssues;
|
||||
|
|
@ -69,7 +68,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
} = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, peekIssueId, peekProjectId } = router.query;
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
|
|
@ -78,9 +77,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// FIXME get from filters
|
||||
const kanbanViewStore: IssueKanBanViewStore = {} as IssueKanBanViewStore;
|
||||
|
||||
const issueIds = issues?.groupedIssueIds || [];
|
||||
|
||||
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
||||
|
|
@ -211,10 +207,19 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
});
|
||||
};
|
||||
|
||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||
kanbanViewStore.handleKanBanToggle(toggle, value);
|
||||
const handleKanbanFilters = (toggle: "group_by" | "sub_group_by", value: string) => {
|
||||
if (workspaceSlug && projectId) {
|
||||
let _kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
|
||||
if (_kanbanFilters.includes(value)) _kanbanFilters = _kanbanFilters.filter((_value) => _value != value);
|
||||
else _kanbanFilters.push(value);
|
||||
issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.KANBAN_FILTERS, {
|
||||
[toggle]: _kanbanFilters,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters || { group_by: [], sub_group_by: [] };
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteIssueModal
|
||||
|
|
@ -230,8 +235,9 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className={`relative h-max min-h-full w-max min-w-full bg-custom-background-90 px-3`}>
|
||||
<div className="relative h-full w-max min-w-full bg-custom-background-90 px-2">
|
||||
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
||||
{/* drag and delete component */}
|
||||
<div
|
||||
className={`fixed left-1/2 -translate-x-1/2 ${
|
||||
isDragStarted ? "z-40" : ""
|
||||
|
|
@ -262,8 +268,8 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={renderQuickActions}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
kanbanFilters={kanbanFilters}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
quickAddCallback={issues?.quickAddIssue}
|
||||
|
|
@ -275,15 +281,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
/>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
|
||||
{/* {workspaceSlug && peekIssueId && peekProjectId && (
|
||||
<IssuePeekOverview
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={peekProjectId.toString()}
|
||||
issueId={peekIssueId.toString()}
|
||||
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as TIssue, EIssueActions.UPDATE)}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ import { observer } from "mobx-react-lite";
|
|||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||
import { IssueProperties } from "../properties/all-properties";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { Tooltip, ControlLink } from "@plane/ui";
|
||||
// types
|
||||
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
import { useRouter } from "next/router";
|
||||
import { useProject } from "hooks/store";
|
||||
import { useApplication, useIssueDetail, useProject } from "hooks/store";
|
||||
|
||||
interface IssueBlockProps {
|
||||
issueId: string;
|
||||
|
|
@ -34,24 +33,23 @@ interface IssueDetailsBlockProps {
|
|||
|
||||
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((props: IssueDetailsBlockProps) => {
|
||||
const { issue, handleIssues, quickActions, isReadOnly, displayProperties } = props;
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
const {
|
||||
router: { workspaceSlug, projectId },
|
||||
} = useApplication();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
|
||||
const updateIssue = (issueToUpdate: TIssue) => {
|
||||
if (issueToUpdate) handleIssues(issueToUpdate, EIssueActions.UPDATE);
|
||||
};
|
||||
|
||||
const handleIssuePeekOverview = () => {
|
||||
const { query } = router;
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id },
|
||||
});
|
||||
};
|
||||
const handleIssuePeekOverview = (issue: TIssue) =>
|
||||
workspaceSlug &&
|
||||
issue &&
|
||||
issue.project_id &&
|
||||
issue.id &&
|
||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -63,11 +61,18 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
|||
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="line-clamp-2 text-sm font-medium text-custom-text-100" onClick={handleIssuePeekOverview}>
|
||||
{issue.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
|
||||
target="_blank"
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
|
||||
>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<span>{issue.name}</span>
|
||||
</Tooltip>
|
||||
</ControlLink>
|
||||
|
||||
<IssueProperties
|
||||
className="flex flex-wrap items-center gap-2 whitespace-nowrap"
|
||||
issue={issue}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
IIssueMap,
|
||||
TSubGroupedIssues,
|
||||
TUnGroupedIssues,
|
||||
TIssueKanbanFilters,
|
||||
} from "@plane/types";
|
||||
// constants
|
||||
import { EIssueActions } from "../types";
|
||||
|
|
@ -30,8 +31,8 @@ export interface IGroupByKanBan {
|
|||
isDragDisabled: boolean;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
|
|
@ -57,8 +58,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||
isDragDisabled,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
enableQuickIssueCreate,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
|
|
@ -77,58 +78,63 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||
|
||||
if (!list) return null;
|
||||
|
||||
const verticalAlignPosition = (_list: IGroupByColumn) => kanBanToggle?.groupByHeaderMinMax.includes(_list.id);
|
||||
const visibilityGroupBy = (_list: IGroupByColumn) =>
|
||||
sub_group_by ? false : kanbanFilters?.group_by.includes(_list.id) ? true : false;
|
||||
|
||||
const isGroupByCreatedBy = group_by === "created_by";
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full gap-3">
|
||||
<div className={`relative w-full flex gap-3 overflow-hidden ${sub_group_by ? "h-full" : "h-full"}`}>
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: IGroupByColumn) => {
|
||||
const verticalPosition = verticalAlignPosition(_list);
|
||||
const groupByVisibilityToggle = visibilityGroupBy(_list);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex flex-shrink-0 flex-col ${!verticalPosition ? `w-[340px]` : ``} group`}
|
||||
key={_list.id}
|
||||
className={`relative flex flex-shrink-0 flex-col h-full group ${
|
||||
groupByVisibilityToggle ? `` : `w-[340px]`
|
||||
}`}
|
||||
>
|
||||
{sub_group_by === null && (
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
<div className="flex-shrink-0 sticky top-0 z-[2] w-full bg-custom-background-90 py-1">
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
icon={_list.icon}
|
||||
title={_list.name}
|
||||
count={(issueIds as TGroupedIssues)?.[_list.id]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={_list.payload}
|
||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<KanbanGroup
|
||||
groupId={_list.id}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
canEditProperties={canEditProperties}
|
||||
verticalPosition={verticalPosition}
|
||||
/>
|
||||
|
||||
{!groupByVisibilityToggle && (
|
||||
<KanbanGroup
|
||||
groupId={_list.id}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
canEditProperties={canEditProperties}
|
||||
groupByVisibilityToggle={groupByVisibilityToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
@ -145,8 +151,8 @@ export interface IKanBan {
|
|||
sub_group_id?: string;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
showEmptyGroup: boolean;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
quickAddCallback?: (
|
||||
|
|
@ -172,8 +178,8 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
sub_group_id = "null",
|
||||
handleIssues,
|
||||
quickActions,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
enableQuickIssueCreate,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
|
|
@ -186,27 +192,25 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
const issueKanBanView = useKanbanView();
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
<GroupByKanBan
|
||||
issuesMap={issuesMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={!issueKanBanView?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
</div>
|
||||
<GroupByKanBan
|
||||
issuesMap={issuesMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={!issueKanBanView?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import useToast from "hooks/use-toast";
|
|||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// types
|
||||
import { TIssue, ISearchIssueResponse } from "@plane/types";
|
||||
import { TIssue, ISearchIssueResponse, TIssueKanbanFilters } from "@plane/types";
|
||||
import { TCreateModalStoreTypes } from "constants/issue";
|
||||
|
||||
interface IHeaderGroupByCard {
|
||||
|
|
@ -21,8 +21,8 @@ interface IHeaderGroupByCard {
|
|||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: any;
|
||||
issuePayload: Partial<TIssue>;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
|
|
@ -36,14 +36,14 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
icon,
|
||||
title,
|
||||
count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
issuePayload,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id);
|
||||
const verticalAlignPosition = sub_group_by ? false : kanbanFilters?.group_by.includes(column_id);
|
||||
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
||||
|
|
@ -117,7 +117,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
{sub_group_by === null && (
|
||||
<div
|
||||
className="flex h-[20px] w-[20px] flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80"
|
||||
onClick={() => handleKanBanToggle("groupByHeaderMinMax", column_id)}
|
||||
onClick={() => handleKanbanFilters("group_by", column_id)}
|
||||
>
|
||||
{verticalAlignPosition ? (
|
||||
<Maximize2 width={14} strokeWidth={2} />
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import React from "react";
|
||||
// lucide icons
|
||||
import { Circle, ChevronDown, ChevronUp } from "lucide-react";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { TIssueKanbanFilters } from "@plane/types";
|
||||
|
||||
interface IHeaderSubGroupByCard {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
count: number;
|
||||
column_id: string;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
}
|
||||
|
||||
export const HeaderSubGroupByCard = observer(
|
||||
({ icon, title, count, column_id, kanBanToggle, handleKanBanToggle }: IHeaderSubGroupByCard) => (
|
||||
({ icon, title, count, column_id, kanbanFilters, handleKanbanFilters }: IHeaderSubGroupByCard) => (
|
||||
<div className={`relative flex w-full flex-shrink-0 flex-row items-center gap-2 rounded-sm p-1.5`}>
|
||||
<div
|
||||
className="flex h-[20px] w-[20px] flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80"
|
||||
onClick={() => handleKanBanToggle("subgroupByIssuesVisibility", column_id)}
|
||||
onClick={() => handleKanbanFilters("sub_group_by", column_id)}
|
||||
>
|
||||
{kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? (
|
||||
{kanbanFilters?.sub_group_by.includes(column_id) ? (
|
||||
<ChevronDown width={14} strokeWidth={2} />
|
||||
) : (
|
||||
<ChevronUp width={14} strokeWidth={2} />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { Droppable } from "@hello-pangea/dnd";
|
||||
// hooks
|
||||
import { useProjectState } from "hooks/store";
|
||||
//components
|
||||
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
||||
//types
|
||||
import {
|
||||
TGroupedIssues,
|
||||
|
|
@ -9,10 +13,6 @@ import {
|
|||
TUnGroupedIssues,
|
||||
} from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
// hooks
|
||||
import { useProjectState } from "hooks/store";
|
||||
//components
|
||||
import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from ".";
|
||||
|
||||
interface IKanbanGroup {
|
||||
groupId: string;
|
||||
|
|
@ -35,7 +35,7 @@ interface IKanbanGroup {
|
|||
viewId?: string;
|
||||
disableIssueCreation?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
verticalPosition: any;
|
||||
groupByVisibilityToggle: boolean;
|
||||
}
|
||||
|
||||
export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
|
|
@ -46,7 +46,6 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||
sub_group_by,
|
||||
issuesMap,
|
||||
displayProperties,
|
||||
verticalPosition,
|
||||
issueIds,
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
|
|
@ -56,80 +55,97 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||
disableIssueCreation,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
groupByVisibilityToggle,
|
||||
} = props;
|
||||
|
||||
// hooks
|
||||
const projectState = useProjectState();
|
||||
|
||||
const prePopulateQuickAddData = (groupByKey: string | null, value: string) => {
|
||||
const prePopulateQuickAddData = (
|
||||
groupByKey: string | null,
|
||||
subGroupByKey: string | null,
|
||||
groupValue: string,
|
||||
subGroupValue: string
|
||||
) => {
|
||||
const defaultState = projectState.projectStates?.find((state) => state.default);
|
||||
let preloadedData: object = { state_id: defaultState?.id };
|
||||
|
||||
if (groupByKey) {
|
||||
if (groupByKey === "state") {
|
||||
preloadedData = { ...preloadedData, state_id: value };
|
||||
preloadedData = { ...preloadedData, state_id: groupValue };
|
||||
} else if (groupByKey === "priority") {
|
||||
preloadedData = { ...preloadedData, priority: value };
|
||||
} else if (groupByKey === "labels" && value != "None") {
|
||||
preloadedData = { ...preloadedData, label_ids: [value] };
|
||||
} else if (groupByKey === "assignees" && value != "None") {
|
||||
preloadedData = { ...preloadedData, assignee_ids: [value] };
|
||||
preloadedData = { ...preloadedData, priority: groupValue };
|
||||
} else if (groupByKey === "labels" && groupValue != "None") {
|
||||
preloadedData = { ...preloadedData, label_ids: [groupValue] };
|
||||
} else if (groupByKey === "assignees" && groupValue != "None") {
|
||||
preloadedData = { ...preloadedData, assignee_ids: [groupValue] };
|
||||
} else if (groupByKey === "created_by") {
|
||||
preloadedData = { ...preloadedData };
|
||||
} else {
|
||||
preloadedData = { ...preloadedData, [groupByKey]: value };
|
||||
preloadedData = { ...preloadedData, [groupByKey]: groupValue };
|
||||
}
|
||||
}
|
||||
|
||||
if (subGroupByKey) {
|
||||
if (subGroupByKey === "state") {
|
||||
preloadedData = { ...preloadedData, state_id: subGroupValue };
|
||||
} else if (subGroupByKey === "priority") {
|
||||
preloadedData = { ...preloadedData, priority: subGroupValue };
|
||||
} else if (subGroupByKey === "labels" && subGroupValue != "None") {
|
||||
preloadedData = { ...preloadedData, label_ids: [subGroupValue] };
|
||||
} else if (subGroupByKey === "assignees" && subGroupValue != "None") {
|
||||
preloadedData = { ...preloadedData, assignee_ids: [subGroupValue] };
|
||||
} else if (subGroupByKey === "created_by") {
|
||||
preloadedData = { ...preloadedData };
|
||||
} else {
|
||||
preloadedData = { ...preloadedData, [subGroupByKey]: subGroupValue };
|
||||
}
|
||||
}
|
||||
|
||||
return preloadedData;
|
||||
};
|
||||
|
||||
const isGroupByCreatedBy = group_by === "created_by";
|
||||
|
||||
return (
|
||||
<div className={`${verticalPosition ? `min-h-[150px] w-[0px] overflow-hidden` : `w-full transition-all`}`}>
|
||||
<div className={`relative w-full h-full overflow-hidden transition-all`}>
|
||||
<Droppable droppableId={`${groupId}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`relative h-full w-full transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
sub_group_by ? `` : `overflow-hidden overflow-y-auto`
|
||||
} ${snapshot.isDraggingOver ? `bg-custom-background-80` : ``}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{!verticalPosition ? (
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={groupId}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
|
||||
displayProperties={displayProperties}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : null}
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={groupId}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
|
||||
displayProperties={displayProperties}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
|
||||
{provided.placeholder}
|
||||
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
|
||||
<KanBanQuickAddIssueForm
|
||||
formKey="name"
|
||||
groupId={groupId}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && prePopulateQuickAddData(group_by, sub_group_by, groupId, sub_group_id)),
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className="sticky bottom-0 z-[0] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
{enableQuickIssueCreate && !disableIssueCreation && !isGroupByCreatedBy && (
|
||||
<KanBanQuickAddIssueForm
|
||||
formKey="name"
|
||||
groupId={groupId}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && prePopulateQuickAddData(group_by, groupId)),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -120,13 +120,13 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
{isOpen ? (
|
||||
<div className="shadow-custom-shadow-sm">
|
||||
<div className="shadow-custom-shadow-sm m-1.5 rounded overflow-hidden">
|
||||
<form
|
||||
ref={ref}
|
||||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
className="flex w-full items-center gap-x-3 border-[0.5px] border-t-0 border-custom-border-100 bg-custom-background-100 px-3"
|
||||
className="flex w-full items-center gap-x-3 bg-custom-background-100 p-3"
|
||||
>
|
||||
<Inputs formKey={formKey} register={register} setFocus={setFocus} projectDetail={projectDetail} />
|
||||
</form>
|
||||
|
|
@ -141,6 +141,6 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
IIssueMap,
|
||||
TSubGroupedIssues,
|
||||
TUnGroupedIssues,
|
||||
TIssueKanbanFilters,
|
||||
} from "@plane/types";
|
||||
// constants
|
||||
import { EIssueActions } from "../types";
|
||||
|
|
@ -25,16 +26,16 @@ interface ISubGroupSwimlaneHeader {
|
|||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
list: IGroupByColumn[];
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
}
|
||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
list,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
}) => (
|
||||
<div className="relative flex h-max min-h-full w-full items-center">
|
||||
{list &&
|
||||
|
|
@ -45,11 +46,11 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
icon={_list.icon}
|
||||
title={_list.name}
|
||||
count={(issueIds as TGroupedIssues)?.[_list.id]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
issuePayload={_list.payload}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -64,8 +65,8 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
|||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
isDragStarted?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
|
|
@ -90,8 +91,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
showEmptyGroup,
|
||||
enableQuickIssueCreate,
|
||||
canEditProperties,
|
||||
|
|
@ -123,13 +124,14 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||
icon={_list.Icon}
|
||||
title={_list.name || ""}
|
||||
count={calculateIssueCount(_list.id)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-b border-dashed border-custom-border-400" />
|
||||
</div>
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && (
|
||||
|
||||
{!kanbanFilters?.sub_group_by.includes(_list.id) && (
|
||||
<div className="relative">
|
||||
<KanBan
|
||||
issuesMap={issuesMap}
|
||||
|
|
@ -140,8 +142,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||
sub_group_id={_list.id}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
|
|
@ -165,8 +167,8 @@ export interface IKanBanSwimLanes {
|
|||
group_by: string | null;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
showEmptyGroup: boolean;
|
||||
isDragStarted?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
|
|
@ -192,8 +194,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||
group_by,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
showEmptyGroup,
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
|
|
@ -227,8 +229,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
list={groupByList}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -243,8 +245,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||
sub_group_by={sub_group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,41 @@ import { IProjectViewIssues } from "store/issue/project-views";
|
|||
import { IWorkspaceIssues } from "store/issue/workspace";
|
||||
import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TUnGroupedIssues } from "@plane/types";
|
||||
|
||||
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
|
||||
const sortOrderDefaultValue = 65535;
|
||||
let currentIssueState = {};
|
||||
|
||||
if (destinationIssues && destinationIssues.length > 0) {
|
||||
if (destinationIndex === 0) {
|
||||
const destinationIssueId = destinationIssues[destinationIndex];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: issueMap[destinationIssueId].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destinationIndex === destinationIssues.length) {
|
||||
const destinationIssueId = destinationIssues[destinationIndex - 1];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: issueMap[destinationIssueId].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
|
||||
const destinationBottomIssueId = destinationIssues[destinationIndex];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: (issueMap[destinationTopIssueId].sort_order + issueMap[destinationBottomIssueId].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
return currentIssueState;
|
||||
};
|
||||
|
||||
export const handleDragDrop = async (
|
||||
source: DraggableLocation | null | undefined,
|
||||
destination: DraggableLocation | null | undefined,
|
||||
|
|
@ -50,7 +85,7 @@ export const handleDragDrop = async (
|
|||
!sourceGroupByColumnId ||
|
||||
!destinationGroupByColumnId ||
|
||||
!sourceSubGroupByColumnId ||
|
||||
!sourceGroupByColumnId
|
||||
!destinationSubGroupByColumnId
|
||||
)
|
||||
return;
|
||||
|
||||
|
|
@ -76,92 +111,49 @@ export const handleDragDrop = async (
|
|||
const [removed] = sourceIssues.splice(source.index, 1);
|
||||
const removedIssueDetail = issueMap[removed];
|
||||
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
project_id: removedIssueDetail?.project_id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...handleSortOrder(destinationIssues, destination.index, issueMap),
|
||||
};
|
||||
|
||||
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...handleSortOrder(destinationIssues, destination.index, issueMap),
|
||||
};
|
||||
|
||||
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
|
||||
if (sourceGroupByColumnId != destinationGroupByColumnId) {
|
||||
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
|
||||
if (groupBy === "state") updateIssue = { ...updateIssue, state_id: destinationGroupByColumnId };
|
||||
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
|
||||
}
|
||||
} else {
|
||||
if (subGroupBy === "state")
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
state: destinationSubGroupByColumnId,
|
||||
state_id: destinationSubGroupByColumnId,
|
||||
priority: destinationGroupByColumnId,
|
||||
};
|
||||
if (subGroupBy === "priority")
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
state: destinationGroupByColumnId,
|
||||
state_id: destinationGroupByColumnId,
|
||||
priority: destinationSubGroupByColumnId,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...handleSortOrder(destinationIssues, destination.index, issueMap),
|
||||
};
|
||||
|
||||
// for horizontal dnd
|
||||
if (sourceColumnId != destinationColumnId) {
|
||||
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
|
||||
if (groupBy === "state") updateIssue = { ...updateIssue, state_id: destinationGroupByColumnId };
|
||||
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
|
||||
}
|
||||
}
|
||||
|
||||
if (updateIssue && updateIssue?.id) {
|
||||
if (viewId) return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue); //, viewId);
|
||||
else return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
|
||||
if (viewId)
|
||||
return await store?.updateIssue(workspaceSlug, updateIssue.project_id, updateIssue.id, updateIssue, viewId);
|
||||
else return await store?.updateIssue(workspaceSlug, updateIssue.project_id, updateIssue.id, updateIssue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
|
||||
const sortOrderDefaultValue = 65535;
|
||||
let currentIssueState = {};
|
||||
|
||||
if (destinationIssues && destinationIssues.length > 0) {
|
||||
if (destinationIndex === 0) {
|
||||
const destinationIssueId = destinationIssues[destinationIndex];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: issueMap[destinationIssueId].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destinationIndex === destinationIssues.length) {
|
||||
const destinationIssueId = destinationIssues[destinationIndex - 1];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: issueMap[destinationIssueId].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
|
||||
const destinationBottomIssueId = destinationIssues[destinationIndex];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: (issueMap[destinationTopIssueId].sort_order + issueMap[destinationBottomIssueId].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
return currentIssueState;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -138,15 +138,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* {workspaceSlug && peekIssueId && peekProjectId && (
|
||||
<IssuePeekOverview
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={peekProjectId.toString()}
|
||||
issueId={peekIssueId.toString()}
|
||||
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as TIssue, EIssueActions.UPDATE)}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
|
|||
href={`/${workspaceSlug}/projects/${projectId}/issues/${issueId}`}
|
||||
target="_blank"
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
className="w-full line-clamp-1 cursor-pointer text-sm font-medium text-custom-text-100"
|
||||
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
|
||||
>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<span>{issue.name}</span>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react";
|
||||
// hooks
|
||||
import { useLabel } from "hooks/store";
|
||||
import { useEstimate, useLabel } from "hooks/store";
|
||||
// components
|
||||
import { IssuePropertyLabels } from "../properties/labels";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
|
@ -29,6 +29,7 @@ export interface IIssueProperties {
|
|||
export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
const { issue, handleIssues, displayProperties, isReadOnly, className } = props;
|
||||
const { labelMap } = useLabel();
|
||||
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
||||
|
||||
const handleState = (stateId: string) => {
|
||||
handleIssues({ ...issue, state_id: stateId });
|
||||
|
|
@ -92,7 +93,6 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* label */}
|
||||
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="labels">
|
||||
<IssuePropertyLabels
|
||||
projectId={issue?.project_id || null}
|
||||
|
|
@ -122,6 +122,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="due_date">
|
||||
<div className="h-5">
|
||||
<DateDropdown
|
||||
minDate={issue.start_date ? new Date(issue.start_date) : undefined}
|
||||
value={issue?.target_date ?? null}
|
||||
onChange={handleTargetDate}
|
||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||
|
|
@ -148,17 +149,19 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* estimates */}
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="estimate">
|
||||
<div className="h-5">
|
||||
<EstimateDropdown
|
||||
value={issue.estimate_point}
|
||||
onChange={handleEstimate}
|
||||
projectId={issue.project_id}
|
||||
disabled={isReadOnly}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
{areEstimatesEnabledForCurrentProject && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="estimate">
|
||||
<div className="h-5">
|
||||
<EstimateDropdown
|
||||
value={issue.estimate_point}
|
||||
onChange={handleEstimate}
|
||||
projectId={issue.project_id}
|
||||
disabled={isReadOnly}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
)}
|
||||
|
||||
{/* extra render properties */}
|
||||
{/* sub-issues */}
|
||||
|
|
|
|||
|
|
@ -5,31 +5,53 @@ import useSWR from "swr";
|
|||
// mobx store
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot } from "components/issues";
|
||||
import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot, ProjectEmptyState } from "components/issues";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
|
||||
export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
|
||||
const {
|
||||
issues: { groupedIssueIds, fetchIssues },
|
||||
issuesFilter: { fetchFilters },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
|
||||
useSWR(workspaceSlug && projectId ? `ARCHIVED_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await fetchFilters(workspaceSlug, projectId);
|
||||
await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader");
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `ARCHIVED_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issues?.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues?.groupedIssueIds ? "mutation" : "init-loader"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
<ArchivedIssueAppliedFiltersRoot />
|
||||
<div className="h-full w-full overflow-auto">
|
||||
<ArchivedIssueListLayout />
|
||||
</div>
|
||||
|
||||
{issues?.loader === "init-loader" ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!issues?.groupedIssueIds ? (
|
||||
// TODO: Replace this with project view empty state
|
||||
<ProjectEmptyState />
|
||||
) : (
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
<ArchivedIssueListLayout />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,36 +21,37 @@ import { Spinner } from "@plane/ui";
|
|||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const CycleLayoutRoot: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { getCycleById } = useCycle();
|
||||
// state
|
||||
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
cycleId: string;
|
||||
};
|
||||
// store hooks
|
||||
const {
|
||||
issues: { loader, groupedIssueIds, fetchIssues },
|
||||
issuesFilter: { issueFilters, fetchFilters },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { getCycleById } = useCycle();
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && cycleId ? `CYCLE_ISSUES_V3_${workspaceSlug}_${projectId}_${cycleId}` : null,
|
||||
workspaceSlug && projectId && cycleId
|
||||
? `CYCLE_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}_${cycleId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && cycleId) {
|
||||
await fetchFilters(workspaceSlug, projectId, cycleId);
|
||||
await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader", cycleId);
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||
await issues?.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues?.groupedIssueIds ? "mutation" : "init-loader",
|
||||
cycleId.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId) : undefined;
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const cycleStatus = cycleDetails?.status.toLocaleLowerCase() ?? "draft";
|
||||
|
||||
if (!workspaceSlug || !projectId || !cycleId) return <></>;
|
||||
return (
|
||||
<>
|
||||
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
|
||||
|
|
@ -59,14 +60,18 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
|
||||
<CycleAppliedFiltersRoot />
|
||||
|
||||
{loader === "init-loader" || !groupedIssueIds ? (
|
||||
{issues?.loader === "init-loader" ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{Object.keys(groupedIssueIds ?? {}).length == 0 ? (
|
||||
<CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} />
|
||||
{!issues?.groupedIssueIds ? (
|
||||
<CycleEmptyState
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
cycleId={cycleId.toString()}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
|
|
|
|||
|
|
@ -2,49 +2,64 @@ import React from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
// mobx store
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { DraftIssueAppliedFiltersRoot } from "../filters/applied-filters/roots/draft-issue";
|
||||
import { DraftIssueListLayout } from "../list/roots/draft-issue-root";
|
||||
import { ProjectEmptyState } from "../empty-states";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
import { DraftKanBanLayout } from "../kanban/roots/draft-issue-root";
|
||||
// constants
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const DraftIssueLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
|
||||
|
||||
const {
|
||||
issues: { loader, groupedIssueIds, fetchIssues },
|
||||
issuesFilter: { issueFilters, fetchFilters },
|
||||
} = useIssues(EIssuesStoreType.DRAFT);
|
||||
|
||||
useSWR(workspaceSlug && projectId ? `DRAFT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await fetchFilters(workspaceSlug, projectId);
|
||||
await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader");
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `DRAFT_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issues?.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues?.groupedIssueIds ? "mutation" : "init-loader"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
<DraftIssueAppliedFiltersRoot />
|
||||
|
||||
{loader === "init-loader" ? (
|
||||
{issues?.loader === "init-loader" ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
<DraftIssueListLayout />
|
||||
) : activeLayout === "kanban" ? (
|
||||
<DraftKanBanLayout />
|
||||
) : null}
|
||||
</div>
|
||||
{!issues?.groupedIssueIds ? (
|
||||
// TODO: Replace this with project view empty state
|
||||
<ProjectEmptyState />
|
||||
) : (
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
<DraftIssueListLayout />
|
||||
) : activeLayout === "kanban" ? (
|
||||
<DraftKanBanLayout />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export * from "./cycle-layout-root";
|
||||
export * from "./all-issue-layout-root";
|
||||
export * from "./module-layout-root";
|
||||
export * from "./project-layout-root";
|
||||
export * from "./module-layout-root";
|
||||
export * from "./cycle-layout-root";
|
||||
export * from "./project-view-layout-root";
|
||||
export * from "./archived-issue-layout-root";
|
||||
export * from "./draft-issue-layout-root";
|
||||
export * from "./all-issue-layout-root";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
|
||||
// mobx store
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
|
|
@ -20,42 +19,48 @@ import { Spinner } from "@plane/ui";
|
|||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ModuleLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
const {
|
||||
issues: { loader, groupedIssueIds, fetchIssues },
|
||||
issuesFilter: { issueFilters, fetchFilters },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||
// hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && moduleId ? `MODULE_ISSUES_V3_${workspaceSlug}_${projectId}_${moduleId}` : null,
|
||||
workspaceSlug && projectId && moduleId
|
||||
? `MODULE_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}_${moduleId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && moduleId) {
|
||||
await fetchFilters(workspaceSlug, projectId, moduleId);
|
||||
await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader", moduleId);
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||
await issues?.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues?.groupedIssueIds ? "mutation" : "init-loader",
|
||||
moduleId.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId || !moduleId) return <></>;
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
<ModuleAppliedFiltersRoot />
|
||||
|
||||
{loader === "init-loader" || !groupedIssueIds ? (
|
||||
{issues?.loader === "init-loader" ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{Object.keys(groupedIssueIds ?? {}).length == 0 ? (
|
||||
<ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
|
||||
{!issues?.groupedIssueIds ? (
|
||||
<ModuleEmptyState
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
moduleId={moduleId.toString()}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
|
|
@ -71,7 +76,6 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
|||
) : null}
|
||||
</div>
|
||||
)}
|
||||
{/* <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
|
|
@ -15,23 +16,27 @@ import {
|
|||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// hooks
|
||||
import { useApplication, useIssues } from "hooks/store";
|
||||
import { useIssues } from "hooks/store";
|
||||
// constants
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ProjectLayoutRoot: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// hooks
|
||||
const {
|
||||
router: { workspaceSlug, projectId },
|
||||
} = useApplication();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
|
||||
useSWR(
|
||||
const {} = useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug, projectId);
|
||||
await issues?.fetchIssues(workspaceSlug, projectId, issues?.groupedIssueIds ? "mutation" : "init-loader");
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issues?.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues?.groupedIssueIds ? "mutation" : "init-loader"
|
||||
);
|
||||
}
|
||||
},
|
||||
{ revalidateOnFocus: false, refreshInterval: 600000, revalidateOnMount: true }
|
||||
|
|
@ -39,6 +44,7 @@ export const ProjectLayoutRoot: FC = observer(() => {
|
|||
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
<ProjectAppliedFiltersRoot />
|
||||
|
|
@ -56,6 +62,13 @@ export const ProjectLayoutRoot: FC = observer(() => {
|
|||
) : (
|
||||
<>
|
||||
<div className="relative h-full w-full overflow-auto bg-custom-background-90">
|
||||
{/* mutation loader */}
|
||||
{issues?.loader === "mutation" && (
|
||||
<div className="fixed w-[40px] h-[40px] z-50 right-[20px] top-[70px] flex justify-center items-center bg-custom-background-80 shadow-sm rounded">
|
||||
<Spinner className="w-4 h-4" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeLayout === "list" ? (
|
||||
<ListLayout />
|
||||
) : activeLayout === "kanban" ? (
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import useSWR from "swr";
|
|||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import {
|
||||
ProjectEmptyState,
|
||||
ProjectViewAppliedFiltersRoot,
|
||||
ProjectViewCalendarLayout,
|
||||
ProjectViewGanttLayout,
|
||||
|
|
@ -17,53 +18,58 @@ import { Spinner } from "@plane/ui";
|
|||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ProjectViewLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, viewId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
viewId?: string;
|
||||
};
|
||||
|
||||
const {
|
||||
issues: { loader, groupedIssueIds, fetchIssues },
|
||||
issuesFilter: { issueFilters, fetchFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const { workspaceSlug, projectId, viewId } = router.query;
|
||||
// hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && viewId) {
|
||||
await fetchFilters(workspaceSlug, projectId, viewId);
|
||||
await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader");
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString());
|
||||
await issues?.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues?.groupedIssueIds ? "mutation" : "init-loader",
|
||||
viewId.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
if (!workspaceSlug || !projectId || !viewId) return <></>;
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
<ProjectViewAppliedFiltersRoot />
|
||||
|
||||
{loader === "init-loader" ? (
|
||||
{issues?.loader === "init-loader" ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
<ProjectViewListLayout />
|
||||
) : activeLayout === "kanban" ? (
|
||||
<ProjectViewKanBanLayout />
|
||||
) : activeLayout === "calendar" ? (
|
||||
<ProjectViewCalendarLayout />
|
||||
) : activeLayout === "gantt_chart" ? (
|
||||
<ProjectViewGanttLayout />
|
||||
) : activeLayout === "spreadsheet" ? (
|
||||
<ProjectViewSpreadsheetLayout />
|
||||
) : null}
|
||||
</div>
|
||||
{!issues?.groupedIssueIds ? (
|
||||
// TODO: Replace this with project view empty state
|
||||
<ProjectEmptyState />
|
||||
) : (
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
<ProjectViewListLayout />
|
||||
) : activeLayout === "kanban" ? (
|
||||
<ProjectViewKanBanLayout />
|
||||
) : activeLayout === "calendar" ? (
|
||||
<ProjectViewCalendarLayout />
|
||||
) : activeLayout === "gantt_chart" ? (
|
||||
<ProjectViewGanttLayout />
|
||||
) : activeLayout === "spreadsheet" ? (
|
||||
<ProjectViewSpreadsheetLayout />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const getGroupByColumns = (
|
|||
case "created_by":
|
||||
return getCreatedByColumns(member) as any;
|
||||
default:
|
||||
if (includeNone) return [{ id: `null`, name: `All Issues`, payload: {}, Icon: undefined }];
|
||||
if (includeNone) return [{ id: `null`, name: `All Issues`, payload: {}, icon: undefined }];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined
|
|||
return {
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
Icon: <div className="w-6 h-6">{renderEmoji(project.emoji || "")}</div>,
|
||||
icon: <div className="w-6 h-6">{renderEmoji(project.emoji || "")}</div>,
|
||||
payload: { project_id: project.id },
|
||||
};
|
||||
}) as any;
|
||||
|
|
@ -61,7 +61,7 @@ const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefine
|
|||
return projectStates.map((state) => ({
|
||||
id: state.id,
|
||||
name: state.name,
|
||||
Icon: (
|
||||
icon: (
|
||||
<div className="w-3.5 h-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} width="14" height="14" />
|
||||
</div>
|
||||
|
|
@ -76,7 +76,7 @@ const getStateGroupColumns = () => {
|
|||
return stateGroups.map((stateGroup) => ({
|
||||
id: stateGroup.key,
|
||||
name: stateGroup.title,
|
||||
Icon: (
|
||||
icon: (
|
||||
<div className="w-3.5 h-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup.key} width="14" height="14" />
|
||||
</div>
|
||||
|
|
@ -91,7 +91,7 @@ const getPriorityColumns = () => {
|
|||
return priorities.map((priority) => ({
|
||||
id: priority.key,
|
||||
name: priority.title,
|
||||
Icon: <PriorityIcon priority={priority?.key} />,
|
||||
icon: <PriorityIcon priority={priority?.key} />,
|
||||
payload: { priority: priority.key },
|
||||
}));
|
||||
};
|
||||
|
|
@ -108,7 +108,7 @@ const getLabelsColumns = (projectLabel: ILabelRootStore) => {
|
|||
return labels.map((label) => ({
|
||||
id: label.id,
|
||||
name: label.name,
|
||||
Icon: (
|
||||
icon: (
|
||||
<div className="w-[12px] h-[12px] rounded-full" style={{ backgroundColor: label.color ? label.color : "#666" }} />
|
||||
),
|
||||
payload: label?.id === "None" ? {} : { label_ids: [label.id] },
|
||||
|
|
@ -128,12 +128,12 @@ const getAssigneeColumns = (member: IMemberRootStore) => {
|
|||
return {
|
||||
id: memberId,
|
||||
name: member?.display_name || "",
|
||||
Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
payload: { assignee_ids: [memberId] },
|
||||
};
|
||||
});
|
||||
|
||||
assigneeColumns.push({ id: "None", name: "None", Icon: <Avatar size="md" />, payload: {} });
|
||||
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
|
||||
|
||||
return assigneeColumns;
|
||||
};
|
||||
|
|
@ -151,7 +151,7 @@ const getCreatedByColumns = (member: IMemberRootStore) => {
|
|||
return {
|
||||
id: memberId,
|
||||
name: member?.display_name || "",
|
||||
Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
payload: {},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue