refactor: modules and states folder structure

This commit is contained in:
Aaryan Khandelwal 2023-02-02 18:04:13 +05:30
parent 8b1bf53831
commit b2eab805e9
25 changed files with 371 additions and 346 deletions

View file

@ -0,0 +1,181 @@
import React, { useCallback } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// react-beautiful-dnd
import { DragDropContext, DropResult } from "react-beautiful-dnd";
// services
import stateService from "services/state.service";
import issuesService from "services/issues.service";
// hooks
import useIssuesProperties from "hooks/use-issue-properties";
import useIssueView from "hooks/use-issue-view";
// components
import SingleBoard from "components/modules/board-view/single-board";
// ui
import { Spinner } from "components/ui";
// types
import { IIssue, IProjectMember, ModuleIssueResponse, UserAuth } from "types";
// constants
import { STATE_LIST, MODULE_ISSUES } from "constants/fetch-keys";
type Props = {
issues: IIssue[];
members: IProjectMember[] | undefined;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| null
>
>;
userAuth: UserAuth;
};
const ModulesBoardView: React.FC<Props> = ({
issues,
members,
openCreateIssueModal,
openIssuesListModal,
handleDeleteIssue,
setPreloadedData,
userAuth,
}) => {
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
const { issueView, groupedByIssues, groupByProperty: selectedGroup } = useIssueView(issues);
const { data: states } = useSWR(
workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
workspaceSlug && projectId
? () => stateService.getStates(workspaceSlug as string, projectId as string)
: null
);
const handleOnDragEnd = useCallback(
(result: DropResult) => {
if (!result.destination) return;
const { source, destination } = result;
if (source.droppableId !== destination.droppableId) {
const sourceGroup = source.droppableId; // source group id
const destinationGroup = destination.droppableId; // destination group id
if (!sourceGroup || !destinationGroup) return;
// removed/dragged item
const removedItem = groupedByIssues[source.droppableId][source.index];
if (selectedGroup === "priority") {
// update the removed item for mutation
removedItem.priority = destinationGroup;
// patch request
issuesService.patchIssue(workspaceSlug as string, projectId as string, removedItem.id, {
priority: destinationGroup,
});
} else if (selectedGroup === "state_detail.name") {
const destinationState = states?.find((s) => s.name === destinationGroup);
const destinationStateId = destinationState?.id;
// update the removed item for mutation
if (!destinationStateId || !destinationState) return;
removedItem.state = destinationStateId;
removedItem.state_detail = destinationState;
// patch request
issuesService.patchIssue(workspaceSlug as string, projectId as string, removedItem.id, {
state: destinationStateId,
});
if (!moduleId) return;
mutate<ModuleIssueResponse[]>(
MODULE_ISSUES(moduleId as string),
(prevData) => {
if (!prevData) return prevData;
const updatedIssues = prevData.map((issue) => {
if (issue.issue_detail.id === removedItem.id) {
return {
...issue,
issue_detail: removedItem,
};
}
return issue;
});
return [...updatedIssues];
},
false
);
}
// remove item from the source group
groupedByIssues[source.droppableId].splice(source.index, 1);
// add item to the destination group
groupedByIssues[destination.droppableId].splice(destination.index, 0, removedItem);
}
},
[workspaceSlug, groupedByIssues, projectId, selectedGroup, states, moduleId]
);
if (issueView !== "kanban") return <></>;
return (
<>
{groupedByIssues ? (
<div className="h-[calc(100vh-157px)] lg:h-[calc(100vh-115px)] w-full">
<DragDropContext onDragEnd={handleOnDragEnd}>
<div className="h-full w-full overflow-hidden">
<div className="h-full w-full">
<div className="flex h-full gap-x-4 overflow-x-auto overflow-y-hidden">
{Object.keys(groupedByIssues).map((singleGroup) => (
<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}
bgColor={
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.color
: "#000000"
}
properties={properties}
openIssuesListModal={openIssuesListModal}
openCreateIssueModal={openCreateIssueModal}
handleDeleteIssue={handleDeleteIssue}
setPreloadedData={setPreloadedData}
stateId={
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.id ?? null
: null
}
userAuth={userAuth}
/>
))}
</div>
</div>
</div>
</DragDropContext>
</div>
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
)}
</>
);
};
export default ModulesBoardView;

View file

@ -0,0 +1,185 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// react-beautiful-dnd
import { Draggable } from "react-beautiful-dnd";
// services
import workspaceService from "services/workspace.service";
// components
import SingleIssue from "components/common/board-view/single-issue";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import BoardHeader from "components/common/board-view/board-header";
// ui
import { CustomMenu } from "components/ui";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import { IIssue, IWorkspaceMember, NestedKeyOf, Properties, UserAuth } from "types";
// fetch-keys
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
type Props = {
properties: Properties;
groupedByIssues: {
[key: string]: IIssue[];
};
selectedGroup: NestedKeyOf<IIssue> | null;
groupTitle: string;
createdBy: string | null;
bgColor?: string;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| null
>
>;
stateId: string | null;
userAuth: UserAuth;
};
const SingleModuleBoard: React.FC<Props> = ({
properties,
groupedByIssues,
selectedGroup,
groupTitle,
createdBy,
bgColor,
openCreateIssueModal,
openIssuesListModal,
handleDeleteIssue,
setPreloadedData,
stateId,
userAuth,
}) => {
// Collapse/Expand
const [isCollapsed, setIsCollapsed] = useState(true);
const router = useRouter();
const { workspaceSlug, moduleId } = 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
);
return (
<div className={`h-full flex-shrink-0 rounded ${!isCollapsed ? "" : "w-80 border bg-gray-50"}`}>
<div className={`${!isCollapsed ? "" : "flex h-full flex-col space-y-3 overflow-y-auto"}`}>
<BoardHeader
addIssueToState={() => {
openCreateIssueModal();
if (selectedGroup !== null) {
setPreloadedData({
state: stateId !== null ? stateId : undefined,
[selectedGroup]: groupTitle,
actionType: "createIssue",
});
}
}}
bgColor={bgColor ?? ""}
createdBy={createdBy}
groupTitle={groupTitle}
groupedByIssues={groupedByIssues}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
selectedGroup={selectedGroup}
/>
<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((issue, index: number) => {
const assignees = [
...(issue?.assignees_list ?? []),
...(issue?.assignees ?? []),
]?.map((assignee) => {
const tempPerson = people?.find((p) => p.member.id === assignee)?.member;
return tempPerson;
});
return (
<Draggable key={issue.id} draggableId={issue.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<SingleIssue
type="module"
typeId={moduleId as string}
issue={issue}
properties={properties}
snapshot={snapshot}
assignees={assignees}
people={people}
handleDeleteIssue={handleDeleteIssue}
userAuth={userAuth}
/>
</div>
)}
</Draggable>
);
})}
{provided.placeholder}
<CustomMenu
label={
<span className="flex items-center gap-1">
<PlusIcon className="h-3 w-3" />
Add issue
</span>
}
className="mt-1"
optionsPosition="left"
noBorder
>
<CustomMenu.MenuItem
onClick={() => {
openCreateIssueModal();
if (selectedGroup !== null) {
setPreloadedData({
state: stateId !== null ? stateId : undefined,
[selectedGroup]: groupTitle,
actionType: "createIssue",
});
}
}}
>
Create new
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
Add an existing issue
</CustomMenu.MenuItem>
</CustomMenu>
</div>
)}
</StrictModeDroppable>
</div>
</div>
);
};
export default SingleModuleBoard;