chore: issue action added and code refactor

This commit is contained in:
Anmol Singh Bhatia 2023-09-25 15:00:35 +05:30
parent 3b8a8cdfa4
commit 06722408b8
15 changed files with 686 additions and 275 deletions

View File

@ -20,6 +20,7 @@ import {
// hooks
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
import useToast from "hooks/use-toast";
import useWorkspaceIssuesFilters from "hooks/use-worskpace-issue-filter";
// services
import issuesService from "services/issues.service";
import trackEventServices from "services/track-event.service";
@ -32,6 +33,7 @@ import {
PROJECT_ISSUES_LIST_WITH_PARAMS,
SUB_ISSUES,
VIEW_ISSUES,
WORKSPACE_VIEW_ISSUES,
} from "constants/fetch-keys";
// types
import {
@ -82,12 +84,51 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
const router = useRouter();
const { workspaceSlug, cycleId, moduleId, viewId } = router.query;
const { workspaceSlug, cycleId, moduleId, viewId, workspaceViewId } = router.query;
const { params } = useSpreadsheetIssuesView();
const { setToastAlert } = useToast();
const workspaceIssuesPath = [
{
params: {
sub_issue: false,
},
path: "workspace-views/all-issues",
},
{
params: {
assignees: user?.id ?? undefined,
sub_issue: false,
},
path: "workspace-views/assigned",
},
{
params: {
created_by: user?.id ?? undefined,
sub_issue: false,
},
path: "workspace-views/created",
},
{
params: {
subscriber: user?.id ?? undefined,
sub_issue: false,
},
path: "workspace-views/subscribed",
},
];
const currentWorkspaceIssuePath = workspaceIssuesPath.find((path) =>
router.pathname.includes(path.path)
);
const { params: workspaceViewParams } = useWorkspaceIssuesFilters(
workspaceSlug?.toString(),
workspaceViewId?.toString()
);
const partialUpdateIssue = useCallback(
(formData: Partial<IIssue>, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
@ -98,6 +139,10 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
: viewId
? VIEW_ISSUES(viewId.toString(), params)
: workspaceViewId
? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), workspaceViewParams)
: currentWorkspaceIssuePath
? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), currentWorkspaceIssuePath?.params)
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params);
if (issue.parent)
@ -153,7 +198,18 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
console.log(error);
});
},
[workspaceSlug, projectId, cycleId, moduleId, viewId, params, user]
[
workspaceSlug,
projectId,
cycleId,
moduleId,
viewId,
workspaceViewId,
currentWorkspaceIssuePath,
workspaceViewParams,
params,
user,
]
);
const openPeekOverview = () => {

View File

@ -9,6 +9,7 @@ import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types";
type Props = {
issue: IIssue;
projectId: string;
index: number;
expandedIssues: string[];
setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>;
@ -24,6 +25,7 @@ type Props = {
export const SpreadsheetIssues: React.FC<Props> = ({
index,
issue,
projectId,
expandedIssues,
setExpandedIssues,
gridTemplateColumns,
@ -55,7 +57,7 @@ export const SpreadsheetIssues: React.FC<Props> = ({
<div>
<SingleSpreadsheetIssue
issue={issue}
projectId={issue.project_detail.id}
projectId={projectId}
index={index}
expanded={isExpanded}
handleToggleExpand={handleToggleExpand}
@ -77,6 +79,7 @@ export const SpreadsheetIssues: React.FC<Props> = ({
<SpreadsheetIssues
key={subIssue.id}
issue={subIssue}
projectId={subIssue.project_detail.id}
index={index}
expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues}

View File

@ -86,6 +86,7 @@ export const SpreadsheetView: React.FC<Props> = ({
key={`${issue.id}_${index}`}
index={index}
issue={issue}
projectId={issue.project_detail.id}
expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues}
gridTemplateColumns={gridTemplateColumns}

View File

@ -161,6 +161,7 @@ export const IssueProperty: React.FC<IIssueProperty> = ({
<div className="flex-shrink-0">
<StateSelect
value={issue.state_detail}
projectId={issue.project_detail.id}
onChange={handleStateChange}
hideDropdownArrow
disabled={!editable}
@ -194,6 +195,7 @@ export const IssueProperty: React.FC<IIssueProperty> = ({
<div className="flex-shrink-0">
<MembersSelect
value={issue.assignees}
projectId={issue.project_detail.id}
onChange={handleAssigneeChange}
membersDetails={issue.assignee_details}
hideDropdownArrow

View File

@ -17,7 +17,6 @@ import { DangerButton, SecondaryButton } from "components/ui";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// types
import type { ICurrentUserResponse, IView } from "types";
import { IWorkspaceView } from "types/workspace-view";
// fetch-keys
import { VIEWS_LIST, WORKSPACE_VIEWS_LIST } from "constants/fetch-keys";
@ -25,7 +24,7 @@ type Props = {
isOpen: boolean;
viewType: "project" | "workspace";
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
data: IView | IWorkspaceView | null;
data: IView | null;
user: ICurrentUserResponse | undefined;
};

View File

@ -12,7 +12,6 @@ import { CustomMenu } from "components/ui";
import viewsService from "services/views.service";
// types
import { IView } from "types";
import { IWorkspaceView } from "types/workspace-view";
// fetch keys
import { VIEWS_LIST } from "constants/fetch-keys";
// hooks
@ -21,7 +20,7 @@ import useToast from "hooks/use-toast";
import { truncateText } from "helpers/string.helper";
type Props = {
view: IView | IWorkspaceView;
view: IView;
viewType: "project" | "workspace";
handleEditView: () => void;
handleDeleteView: () => void;

View File

@ -159,7 +159,7 @@ export const WORKSPACE_VIEWS_LIST = (workspaceSlug: string) =>
`WORKSPACE_VIEWS_LIST_${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_VIEW_DETAILS = (workspaceViewId: string) =>
`WORKSPACE_VIEW_DETAILS_${workspaceViewId.toUpperCase()}`;
export const WORKSPACE_VIEW_ISSUES = (workspaceViewId: string, params: any) => {
export const WORKSPACE_VIEW_ISSUES = (workspaceViewId: string, params?: any) => {
if (!params) return `WORKSPACE_VIEW_ISSUES_${workspaceViewId.toUpperCase()}`;
const paramsKey = paramsToKey(params);

View File

@ -1,3 +1,4 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
@ -9,12 +10,12 @@ import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
import workspaceService from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
import { SpreadsheetView } from "components/core";
import { WorkspaceViewsNavigation } from "components/workspace/views/workpace-view-navigation";
import { WorkspaceIssuesViewOptions } from "components/issues/workspace-views/workspace-issue-view-option";
import { CreateUpdateViewModal } from "components/views";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// ui
import { EmptyState, PrimaryButton } from "components/ui";
// icons
@ -24,8 +25,28 @@ import { CheckCircle } from "lucide-react";
import emptyView from "public/empty-state/view.svg";
// fetch-keys
import { WORKSPACE_VIEW_DETAILS, WORKSPACE_VIEW_ISSUES } from "constants/fetch-keys";
// types
import { IIssue } from "types";
const WorkspaceViewAllIssue: React.FC = () => {
const [createViewModal, setCreateViewModal] = useState<any>(null);
// create issue modal
const [createIssueModal, setCreateIssueModal] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
// update issue modal
const [editIssueModal, setEditIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<
(IIssue & { actionType: "edit" | "delete" }) | undefined
>(undefined);
// delete issue modal
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query;
@ -58,67 +79,138 @@ const WorkspaceViewAllIssue: React.FC = () => {
workspaceSlug ? () => workspaceService.getViewIssues(workspaceSlug.toString(), params) : null
);
const makeIssueCopy = useCallback(
(issue: IIssue) => {
setCreateIssueModal(true);
setPreloadedData({ ...issue, name: `${issue.name} (Copy)`, actionType: "createIssue" });
},
[setCreateIssueModal, setPreloadedData]
);
const handleEditIssue = useCallback(
(issue: IIssue) => {
setEditIssueModal(true);
setIssueToEdit({
...issue,
actionType: "edit",
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
module: issue.issue_module ? issue.issue_module.module : null,
});
},
[setEditIssueModal, setIssueToEdit]
);
const handleDeleteIssue = useCallback(
(issue: IIssue) => {
setDeleteIssueModal(true);
setIssueToDelete(issue);
},
[setDeleteIssueModal, setIssueToDelete]
);
const handleIssueAction = useCallback(
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
if (action === "copy") makeIssueCopy(issue);
else if (action === "edit") handleEditIssue(issue);
else if (action === "delete") handleDeleteIssue(issue);
},
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
);
return (
<IssueViewContextProvider>
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<CreateUpdateIssueModal
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
handleClose={() => setCreateIssueModal(false)}
prePopulateData={{
...preloadedData,
}}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateIssueModal
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
handleClose={() => setEditIssueModal(false)}
data={issueToEdit}
onSubmit={async () => {
mutateIssues();
}}
/>
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueToDelete}
user={user}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateViewModal
isOpen={createViewModal !== null}
handleClose={() => setCreateViewModal(null)}
viewType="workspace"
preLoadedData={createViewModal}
user={user}
/>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation handleAddView={() => setCreateViewModal(true)} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation user={user} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={handleIssueAction}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={(...args) => {}}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
</div>
)}
</div>
</div>
)}
</div>
</WorkspaceAuthorizationLayout>
</IssueViewContextProvider>
</div>
</WorkspaceAuthorizationLayout>
);
};

View File

@ -1,3 +1,4 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
@ -9,12 +10,12 @@ import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
import workspaceService from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
import { SpreadsheetView } from "components/core";
import { WorkspaceViewsNavigation } from "components/workspace/views/workpace-view-navigation";
import { WorkspaceIssuesViewOptions } from "components/issues/workspace-views/workspace-issue-view-option";
import { CreateUpdateViewModal } from "components/views";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// ui
import { EmptyState, PrimaryButton } from "components/ui";
// icons
@ -24,8 +25,28 @@ import { CheckCircle } from "lucide-react";
import emptyView from "public/empty-state/view.svg";
// fetch-keys
import { WORKSPACE_VIEW_DETAILS, WORKSPACE_VIEW_ISSUES } from "constants/fetch-keys";
// types
import { IIssue } from "types";
const WorkspaceViewAssignedIssue: React.FC = () => {
const [createViewModal, setCreateViewModal] = useState<any>(null);
// create issue modal
const [createIssueModal, setCreateIssueModal] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
// update issue modal
const [editIssueModal, setEditIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<
(IIssue & { actionType: "edit" | "delete" }) | undefined
>(undefined);
// delete issue modal
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query;
@ -58,67 +79,138 @@ const WorkspaceViewAssignedIssue: React.FC = () => {
workspaceSlug ? () => workspaceService.getViewIssues(workspaceSlug.toString(), params) : null
);
const makeIssueCopy = useCallback(
(issue: IIssue) => {
setCreateIssueModal(true);
setPreloadedData({ ...issue, name: `${issue.name} (Copy)`, actionType: "createIssue" });
},
[setCreateIssueModal, setPreloadedData]
);
const handleEditIssue = useCallback(
(issue: IIssue) => {
setEditIssueModal(true);
setIssueToEdit({
...issue,
actionType: "edit",
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
module: issue.issue_module ? issue.issue_module.module : null,
});
},
[setEditIssueModal, setIssueToEdit]
);
const handleDeleteIssue = useCallback(
(issue: IIssue) => {
setDeleteIssueModal(true);
setIssueToDelete(issue);
},
[setDeleteIssueModal, setIssueToDelete]
);
const handleIssueAction = useCallback(
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
if (action === "copy") makeIssueCopy(issue);
else if (action === "edit") handleEditIssue(issue);
else if (action === "delete") handleDeleteIssue(issue);
},
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
);
return (
<IssueViewContextProvider>
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<CreateUpdateIssueModal
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
handleClose={() => setCreateIssueModal(false)}
prePopulateData={{
...preloadedData,
}}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateIssueModal
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
handleClose={() => setEditIssueModal(false)}
data={issueToEdit}
onSubmit={async () => {
mutateIssues();
}}
/>
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueToDelete}
user={user}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateViewModal
isOpen={createViewModal !== null}
handleClose={() => setCreateViewModal(null)}
viewType="workspace"
preLoadedData={createViewModal}
user={user}
/>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation handleAddView={() => setCreateViewModal(true)} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation user={user} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={handleIssueAction}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={(...args) => {}}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
</div>
)}
</div>
</div>
)}
</div>
</WorkspaceAuthorizationLayout>
</IssueViewContextProvider>
</div>
</WorkspaceAuthorizationLayout>
);
};

View File

@ -1,3 +1,5 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
@ -9,12 +11,12 @@ import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
import workspaceService from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
import { SpreadsheetView } from "components/core";
import { WorkspaceViewsNavigation } from "components/workspace/views/workpace-view-navigation";
import { WorkspaceIssuesViewOptions } from "components/issues/workspace-views/workspace-issue-view-option";
import { CreateUpdateViewModal } from "components/views";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// ui
import { EmptyState, PrimaryButton } from "components/ui";
// icons
@ -24,8 +26,28 @@ import { CheckCircle } from "lucide-react";
import emptyView from "public/empty-state/view.svg";
// fetch-keys
import { WORKSPACE_VIEW_DETAILS, WORKSPACE_VIEW_ISSUES } from "constants/fetch-keys";
// types
import { IIssue } from "types";
const WorkspaceViewCreatedIssue: React.FC = () => {
const [createViewModal, setCreateViewModal] = useState<any>(null);
// create issue modal
const [createIssueModal, setCreateIssueModal] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
// update issue modal
const [editIssueModal, setEditIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<
(IIssue & { actionType: "edit" | "delete" }) | undefined
>(undefined);
// delete issue modal
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query;
@ -58,67 +80,138 @@ const WorkspaceViewCreatedIssue: React.FC = () => {
workspaceSlug ? () => workspaceService.getViewIssues(workspaceSlug.toString(), params) : null
);
const makeIssueCopy = useCallback(
(issue: IIssue) => {
setCreateIssueModal(true);
setPreloadedData({ ...issue, name: `${issue.name} (Copy)`, actionType: "createIssue" });
},
[setCreateIssueModal, setPreloadedData]
);
const handleEditIssue = useCallback(
(issue: IIssue) => {
setEditIssueModal(true);
setIssueToEdit({
...issue,
actionType: "edit",
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
module: issue.issue_module ? issue.issue_module.module : null,
});
},
[setEditIssueModal, setIssueToEdit]
);
const handleDeleteIssue = useCallback(
(issue: IIssue) => {
setDeleteIssueModal(true);
setIssueToDelete(issue);
},
[setDeleteIssueModal, setIssueToDelete]
);
const handleIssueAction = useCallback(
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
if (action === "copy") makeIssueCopy(issue);
else if (action === "edit") handleEditIssue(issue);
else if (action === "delete") handleDeleteIssue(issue);
},
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
);
return (
<IssueViewContextProvider>
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<CreateUpdateIssueModal
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
handleClose={() => setCreateIssueModal(false)}
prePopulateData={{
...preloadedData,
}}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateIssueModal
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
handleClose={() => setEditIssueModal(false)}
data={issueToEdit}
onSubmit={async () => {
mutateIssues();
}}
/>
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueToDelete}
user={user}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateViewModal
isOpen={createViewModal !== null}
handleClose={() => setCreateViewModal(null)}
viewType="workspace"
preLoadedData={createViewModal}
user={user}
/>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation handleAddView={() => setCreateViewModal(true)} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation user={user} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={handleIssueAction}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={(...args) => {}}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
</div>
)}
</div>
</div>
)}
</div>
</WorkspaceAuthorizationLayout>
</IssueViewContextProvider>
</div>
</WorkspaceAuthorizationLayout>
);
};

View File

@ -25,7 +25,7 @@ import { PhotoFilterOutlined } from "@mui/icons-material";
import emptyView from "public/empty-state/view.svg";
// types
import type { NextPage } from "next";
import { IWorkspaceView } from "types/workspace-view";
import { IView } from "types";
// constants
import { WORKSPACE_VIEWS_LIST } from "constants/fetch-keys";
// helper
@ -33,17 +33,17 @@ import { truncateText } from "helpers/string.helper";
const WorkspaceViews: NextPage = () => {
const [createUpdateViewModal, setCreateUpdateViewModal] = useState(false);
const [selectedViewToUpdate, setSelectedViewToUpdate] = useState<IWorkspaceView | null>(null);
const [selectedViewToUpdate, setSelectedViewToUpdate] = useState<IView | null>(null);
const [deleteViewModal, setDeleteViewModal] = useState(false);
const [selectedViewToDelete, setSelectedViewToDelete] = useState<IWorkspaceView | null>(null);
const [selectedViewToDelete, setSelectedViewToDelete] = useState<IView | null>(null);
const handleEditView = (view: IWorkspaceView) => {
const handleEditView = (view: IView) => {
setSelectedViewToUpdate(view);
setCreateUpdateViewModal(true);
};
const handleDeleteView = (view: IWorkspaceView) => {
const handleDeleteView = (view: IView) => {
setSelectedViewToDelete(view);
setDeleteViewModal(true);
};

View File

@ -1,3 +1,5 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
@ -9,12 +11,12 @@ import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
import workspaceService from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
import { SpreadsheetView } from "components/core";
import { WorkspaceViewsNavigation } from "components/workspace/views/workpace-view-navigation";
import { WorkspaceIssuesViewOptions } from "components/issues/workspace-views/workspace-issue-view-option";
import { CreateUpdateViewModal } from "components/views";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// ui
import { EmptyState, PrimaryButton } from "components/ui";
// icons
@ -24,8 +26,28 @@ import { CheckCircle } from "lucide-react";
import emptyView from "public/empty-state/view.svg";
// fetch-keys
import { WORKSPACE_VIEW_DETAILS, WORKSPACE_VIEW_ISSUES } from "constants/fetch-keys";
// types
import { IIssue } from "types";
const WorkspaceViewSubscribedIssue: React.FC = () => {
const [createViewModal, setCreateViewModal] = useState<any>(null);
// create issue modal
const [createIssueModal, setCreateIssueModal] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
// update issue modal
const [editIssueModal, setEditIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<
(IIssue & { actionType: "edit" | "delete" }) | undefined
>(undefined);
// delete issue modal
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query;
@ -58,67 +80,138 @@ const WorkspaceViewSubscribedIssue: React.FC = () => {
workspaceSlug ? () => workspaceService.getViewIssues(workspaceSlug.toString(), params) : null
);
const makeIssueCopy = useCallback(
(issue: IIssue) => {
setCreateIssueModal(true);
setPreloadedData({ ...issue, name: `${issue.name} (Copy)`, actionType: "createIssue" });
},
[setCreateIssueModal, setPreloadedData]
);
const handleEditIssue = useCallback(
(issue: IIssue) => {
setEditIssueModal(true);
setIssueToEdit({
...issue,
actionType: "edit",
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
module: issue.issue_module ? issue.issue_module.module : null,
});
},
[setEditIssueModal, setIssueToEdit]
);
const handleDeleteIssue = useCallback(
(issue: IIssue) => {
setDeleteIssueModal(true);
setIssueToDelete(issue);
},
[setDeleteIssueModal, setIssueToDelete]
);
const handleIssueAction = useCallback(
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
if (action === "copy") makeIssueCopy(issue);
else if (action === "edit") handleEditIssue(issue);
else if (action === "delete") handleDeleteIssue(issue);
},
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
);
return (
<IssueViewContextProvider>
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
<WorkspaceAuthorizationLayout
breadcrumbs={
<div className="flex gap-2 items-center">
<CheckCircle className="h-[18px] w-[18px] stroke-[1.5]" />
<span className="text-sm font-medium">
{viewDetails ? `${viewDetails.name} Issues` : "Workspace Issues"}
</span>
</div>
}
right={
<div className="flex items-center gap-2">
<WorkspaceIssuesViewOptions />
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<CreateUpdateIssueModal
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
handleClose={() => setCreateIssueModal(false)}
prePopulateData={{
...preloadedData,
}}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateIssueModal
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
handleClose={() => setEditIssueModal(false)}
data={issueToEdit}
onSubmit={async () => {
mutateIssues();
}}
/>
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueToDelete}
user={user}
onSubmit={async () => {
mutateIssues();
}}
/>
<CreateUpdateViewModal
isOpen={createViewModal !== null}
handleClose={() => setCreateViewModal(null)}
viewType="workspace"
preLoadedData={createViewModal}
user={user}
/>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation handleAddView={() => setCreateViewModal(true)} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full border-b border-custom-border-300">
<WorkspaceViewsNavigation user={user} />
{error ? (
<EmptyState
image={emptyView}
title="View does not exist"
description="The view you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other views",
onClick: () => router.push(`/${workspaceSlug}/workspace-views`),
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={handleIssueAction}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
) : (
<div className="h-full w-full flex flex-col">
<SpreadsheetView
spreadsheetIssues={viewIssues}
mutateIssues={mutateIssues}
handleIssueAction={(...args) => {}}
disableUserActions={false}
user={user}
userAuth={{
isGuest: false,
isMember: false,
isOwner: false,
isViewer: false,
}}
/>
</div>
)}
</div>
</div>
)}
</div>
</WorkspaceAuthorizationLayout>
</IssueViewContextProvider>
</div>
</WorkspaceAuthorizationLayout>
);
};

View File

@ -14,9 +14,9 @@ import {
ICurrentUserResponse,
IWorkspaceBulkInviteFormData,
IWorkspaceViewProps,
IView,
IIssueFilterOptions,
} from "types";
import { IWorkspaceView } from "types/workspace-view";
class WorkspaceService extends APIService {
constructor() {
@ -264,7 +264,7 @@ class WorkspaceService extends APIService {
});
}
async createView(workspaceSlug: string, data: IWorkspaceView): Promise<any> {
async createView(workspaceSlug: string, data: IView): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/views/`, data)
.then((response) => response?.data)
.catch((error) => {
@ -272,11 +272,7 @@ class WorkspaceService extends APIService {
});
}
async updateView(
workspaceSlug: string,
viewId: string,
data: Partial<IWorkspaceView>
): Promise<any> {
async updateView(workspaceSlug: string, viewId: string, data: Partial<IView>): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/views/${viewId}/`, data)
.then((response) => response?.data)
.catch((error) => {
@ -292,7 +288,7 @@ class WorkspaceService extends APIService {
});
}
async getAllViews(workspaceSlug: string): Promise<IWorkspaceView[]> {
async getAllViews(workspaceSlug: string): Promise<IView[]> {
return this.get(`/api/workspaces/${workspaceSlug}/views/`)
.then((response) => response?.data)
.catch((error) => {
@ -300,7 +296,7 @@ class WorkspaceService extends APIService {
});
}
async getViewDetails(workspaceSlug: string, viewId: string): Promise<IWorkspaceView> {
async getViewDetails(workspaceSlug: string, viewId: string): Promise<IView> {
return this.get(`/api/workspaces/${workspaceSlug}/views/${viewId}/`)
.then((response) => response?.data)
.catch((error) => {

View File

@ -14,6 +14,11 @@ export interface IView {
query_data: IIssueFilterOptions;
project: string;
workspace: string;
workspace_detail: {
id: string;
name: string;
slug: string;
};
}
export interface IQuery {
@ -25,4 +30,5 @@ export interface IQuery {
start_date: string[] | null;
target_date: string[] | null;
type: "active" | "backlog" | null;
projects: string[] | null;
}

View File

@ -1,21 +0,0 @@
import { IIssueFilterOptions } from "./view-props";
export interface IWorkspaceView {
access: string;
created_at: Date;
created_by: string;
description: string;
id: string;
name: string;
query: IIssueFilterOptions;
query_data: IIssueFilterOptions;
updated_at: Date;
updated_by: string;
is_favorite: boolean;
workspace: string;
workspace_detail: {
id: string;
name: string;
slug: string;
};
}