forked from github/plane
refactor: created global component for kanban board
This commit is contained in:
@@ -5,16 +5,14 @@ import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// react-beautiful-dnd
|
||||
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd";
|
||||
// hook
|
||||
import useIssuesProperties from "hooks/use-issue-properties";
|
||||
import useIssueView from "hooks/use-issue-view";
|
||||
// services
|
||||
import stateServices from "services/state.service";
|
||||
import issuesServices from "services/issues.service";
|
||||
import projectService from "services/project.service";
|
||||
// components
|
||||
import SingleBoard from "components/issues/board-view/single-board";
|
||||
import { CommonSingleBoard } from "components/core/board-view/single-board";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
||||
// ui
|
||||
@@ -22,7 +20,7 @@ import { Spinner } from "components/ui";
|
||||
// types
|
||||
import type { IState, IIssue, IssueResponse, UserAuth } from "types";
|
||||
// fetch-keys
|
||||
import { STATE_LIST, PROJECT_ISSUES_LIST, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||
import { STATE_LIST, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
issues: IIssue[];
|
||||
@@ -30,7 +28,7 @@ type Props = {
|
||||
userAuth: UserAuth;
|
||||
};
|
||||
|
||||
const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) => {
|
||||
export const IssuesBoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) => {
|
||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||
const [isIssueDeletionOpen, setIsIssueDeletionOpen] = useState(false);
|
||||
const [issueDeletionData, setIssueDeletionData] = useState<IIssue | undefined>();
|
||||
@@ -50,21 +48,6 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: members } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||
: null,
|
||||
{
|
||||
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||
if (err?.status === 403) return;
|
||||
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
||||
|
||||
const handleOnDragEnd = useCallback(
|
||||
(result: DropResult) => {
|
||||
if (!result.destination || !workspaceSlug || !projectId) return;
|
||||
@@ -208,36 +191,42 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<div className="flex h-full gap-x-4 overflow-x-auto overflow-y-hidden">
|
||||
{Object.keys(groupedByIssues).map((singleGroup, index) => (
|
||||
<SingleBoard
|
||||
key={singleGroup}
|
||||
selectedGroup={selectedGroup}
|
||||
groupTitle={singleGroup}
|
||||
createdBy={
|
||||
selectedGroup === "created_by"
|
||||
? members?.find((m) => m.member.id === singleGroup)?.member
|
||||
.first_name ?? "loading..."
|
||||
: null
|
||||
}
|
||||
groupedByIssues={groupedByIssues}
|
||||
index={index}
|
||||
setIsIssueOpen={setCreateIssueModal}
|
||||
properties={properties}
|
||||
setPreloadedData={setPreloadedData}
|
||||
stateId={
|
||||
selectedGroup === "state_detail.name"
|
||||
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
||||
: null
|
||||
}
|
||||
bgColor={
|
||||
selectedGroup === "state_detail.name"
|
||||
? states?.find((s) => s.name === singleGroup)?.color
|
||||
: "#000000"
|
||||
}
|
||||
handleDeleteIssue={handleDeleteIssue}
|
||||
userAuth={userAuth}
|
||||
/>
|
||||
))}
|
||||
{Object.keys(groupedByIssues).map((singleGroup, index) => {
|
||||
const stateId =
|
||||
selectedGroup === "state_detail.name"
|
||||
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Draggable key={singleGroup} draggableId={singleGroup} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<CommonSingleBoard
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
bgColor={
|
||||
selectedGroup === "state_detail.name"
|
||||
? states?.find((s) => s.name === singleGroup)?.color
|
||||
: "#000000"
|
||||
}
|
||||
groupTitle={singleGroup}
|
||||
groupedByIssues={groupedByIssues}
|
||||
selectedGroup={selectedGroup}
|
||||
addIssueToState={() => {
|
||||
setCreateIssueModal(true);
|
||||
if (selectedGroup)
|
||||
setPreloadedData({
|
||||
state: stateId !== null ? stateId : undefined,
|
||||
[selectedGroup]: singleGroup,
|
||||
actionType: "createIssue",
|
||||
});
|
||||
}}
|
||||
handleDeleteIssue={handleDeleteIssue}
|
||||
userAuth={userAuth}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
@@ -254,5 +243,3 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardView;
|
||||
@@ -1,174 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// react-beautiful-dnd
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
// components
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import SingleIssue from "components/common/board-view/single-issue";
|
||||
import BoardHeader from "components/common/board-view/board-header";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// types
|
||||
import { IIssue, Properties, NestedKeyOf, IWorkspaceMember, UserAuth } from "types";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
selectedGroup: NestedKeyOf<IIssue> | null;
|
||||
groupTitle: string;
|
||||
groupedByIssues: {
|
||||
[key: string]: IIssue[];
|
||||
};
|
||||
index: number;
|
||||
setIsIssueOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
properties: Properties;
|
||||
setPreloadedData: React.Dispatch<
|
||||
React.SetStateAction<
|
||||
| (Partial<IIssue> & {
|
||||
actionType: "createIssue" | "edit" | "delete";
|
||||
})
|
||||
| undefined
|
||||
>
|
||||
>;
|
||||
bgColor?: string;
|
||||
stateId: string | null;
|
||||
createdBy: string | null;
|
||||
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
userAuth: UserAuth;
|
||||
};
|
||||
|
||||
const SingleBoard: React.FC<Props> = ({
|
||||
selectedGroup,
|
||||
groupTitle,
|
||||
groupedByIssues,
|
||||
index,
|
||||
setIsIssueOpen,
|
||||
properties,
|
||||
setPreloadedData,
|
||||
bgColor = "#0f2b16",
|
||||
stateId,
|
||||
createdBy,
|
||||
handleDeleteIssue,
|
||||
userAuth,
|
||||
}) => {
|
||||
// Collapse/Expand
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
if (selectedGroup === "priority")
|
||||
groupTitle === "high"
|
||||
? (bgColor = "#dc2626")
|
||||
: groupTitle === "medium"
|
||||
? (bgColor = "#f97316")
|
||||
: groupTitle === "low"
|
||||
? (bgColor = "#22c55e")
|
||||
: (bgColor = "#ff0000");
|
||||
|
||||
const { data: people } = useSWR<IWorkspaceMember[]>(
|
||||
workspaceSlug ? WORKSPACE_MEMBERS : null,
|
||||
workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null
|
||||
);
|
||||
|
||||
const addIssueToState = () => {
|
||||
setIsIssueOpen(true);
|
||||
if (selectedGroup)
|
||||
setPreloadedData({
|
||||
state: stateId !== null ? stateId : undefined,
|
||||
[selectedGroup]: groupTitle,
|
||||
actionType: "createIssue",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable draggableId={groupTitle} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className={`h-full flex-shrink-0 rounded ${
|
||||
snapshot.isDragging ? "border-theme shadow-lg" : ""
|
||||
} ${!isCollapsed ? "" : "w-80 border bg-gray-50"}`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
>
|
||||
<div
|
||||
className={`${!isCollapsed ? "" : "flex h-full flex-col space-y-3 overflow-y-auto"}`}
|
||||
>
|
||||
<BoardHeader
|
||||
addIssueToState={addIssueToState}
|
||||
bgColor={bgColor}
|
||||
createdBy={createdBy}
|
||||
groupTitle={groupTitle}
|
||||
groupedByIssues={groupedByIssues}
|
||||
isCollapsed={isCollapsed}
|
||||
setIsCollapsed={setIsCollapsed}
|
||||
selectedGroup={selectedGroup}
|
||||
provided={provided}
|
||||
/>
|
||||
<StrictModeDroppable key={groupTitle} droppableId={groupTitle}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className={`mt-3 h-full space-y-3 overflow-y-auto px-3 pb-3 ${
|
||||
snapshot.isDraggingOver ? "bg-indigo-50 bg-opacity-50" : ""
|
||||
} ${!isCollapsed ? "hidden" : "block"}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{groupedByIssues[groupTitle].map((childIssue, index: number) => {
|
||||
const assignees = [
|
||||
...(childIssue?.assignees_list ?? []),
|
||||
...(childIssue?.assignees ?? []),
|
||||
]?.map((assignee) => {
|
||||
const tempPerson = people?.find((p) => p.member.id === assignee)?.member;
|
||||
|
||||
return tempPerson;
|
||||
});
|
||||
|
||||
return (
|
||||
<Draggable key={childIssue.id} draggableId={childIssue.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
<SingleIssue
|
||||
issue={childIssue}
|
||||
properties={properties}
|
||||
snapshot={snapshot}
|
||||
people={people}
|
||||
assignees={assignees}
|
||||
handleDeleteIssue={handleDeleteIssue}
|
||||
userAuth={userAuth}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
{provided.placeholder}
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center rounded p-2 text-xs font-medium outline-none duration-300 hover:bg-gray-100"
|
||||
onClick={addIssueToState}
|
||||
>
|
||||
<PlusIcon className="mr-1 h-3 w-3" />
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
export default SingleBoard;
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from "./board-view";
|
||||
export * from "./comment";
|
||||
export * from "./sidebar-select";
|
||||
export * from "./activity";
|
||||
export * from "./board-view";
|
||||
export * from "./delete-issue-modal";
|
||||
export * from "./description-form";
|
||||
export * from "./form";
|
||||
|
||||
@@ -16,7 +16,7 @@ import workspaceService from "services/workspace.service";
|
||||
import { Spinner } from "components/ui";
|
||||
// components
|
||||
import { CreateUpdateIssueModal } from "components/issues/modal";
|
||||
import SingleListIssue from "components/common/list-view/single-issue";
|
||||
import SingleListIssue from "components/core/list-view/single-issue";
|
||||
// helpers
|
||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||
// types
|
||||
|
||||
@@ -209,6 +209,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
|
||||
const payload: Partial<IIssue> = {
|
||||
...formData,
|
||||
description: formData.description ? formData.description : "",
|
||||
description_html: formData.description_html ? formData.description_html : "<p></p>",
|
||||
};
|
||||
|
||||
if (!data) await createIssue(payload);
|
||||
|
||||
@@ -177,7 +177,6 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
||||
<form>
|
||||
<Combobox
|
||||
onChange={(val: string) => {
|
||||
console.log("Triggered");
|
||||
const selectedIssues = watchBlocked("blocked_issue_ids");
|
||||
if (selectedIssues.includes(val))
|
||||
setValue(
|
||||
|
||||
@@ -151,7 +151,7 @@ export const SubIssuesListModal: React.FC<Props> = ({ isOpen, handleClose, paren
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`block h-1.5 w-1.5 rounded-full`}
|
||||
className="block flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
}}
|
||||
|
||||
@@ -113,7 +113,7 @@ export const SubIssuesList: FC<SubIssueListProps> = ({
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}>
|
||||
<a className="flex items-center gap-2 rounded text-xs">
|
||||
<span
|
||||
className={`block h-1.5 w-1.5 rounded-full`}
|
||||
className="block flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user