- {states && projectDetails && orderedStateGroups ? (
- Object.keys(orderedStateGroups).map((key) => {
- if (orderedStateGroups[key].length !== 0)
- return (
-
-
-
{key}
-
-
-
- {key === activeGroup && (
-
{
- setActiveGroup(null);
- setSelectedState(null);
- }}
- data={null}
- selectedGroup={key as keyof StateGroup}
- user={user}
- />
- )}
- {orderedStateGroups[key].map((state, index) =>
- state.id !== selectedState ? (
- setSelectedState(state.id)}
- handleDeleteState={() => setSelectDeleteState(state.id)}
+ }>
+
+
+
+
States
+
+
+ {states && projectDetails && orderedStateGroups ? (
+ Object.keys(orderedStateGroups).map((key) => {
+ if (orderedStateGroups[key].length !== 0)
+ return (
+
+
+
{key}
+
+
+
+ {key === activeGroup && (
+
{
+ setActiveGroup(null);
+ setSelectedState(null);
+ }}
+ data={null}
+ selectedGroup={key as keyof StateGroup}
user={user}
/>
- ) : (
-
- {
- setActiveGroup(null);
- setSelectedState(null);
- }}
- groupLength={orderedStateGroups[key].length}
- data={statesList?.find((state) => state.id === selectedState) ?? null}
- selectedGroup={key as keyof StateGroup}
+ )}
+ {orderedStateGroups[key].map((state, index) =>
+ state.id !== selectedState ? (
+ setSelectedState(state.id)}
+ handleDeleteState={() => setSelectDeleteState(state.id)}
user={user}
/>
-
- )
- )}
+ ) : (
+
+ {
+ setActiveGroup(null);
+ setSelectedState(null);
+ }}
+ groupLength={orderedStateGroups[key].length}
+ data={statesList?.find((state) => state.id === selectedState) ?? null}
+ selectedGroup={key as keyof StateGroup}
+ user={user}
+ />
+
+ )
+ )}
+
-
- );
- })
- ) : (
-
-
-
-
-
-
- )}
+ );
+ })
+ ) : (
+
+
+
+
+
+
+ )}
+
-
-
+
+
>
);
};
diff --git a/web/pages/[workspaceSlug]/settings/billing.tsx b/web/pages/[workspaceSlug]/settings/billing.tsx
index 65a645217..8178d000b 100644
--- a/web/pages/[workspaceSlug]/settings/billing.tsx
+++ b/web/pages/[workspaceSlug]/settings/billing.tsx
@@ -1,6 +1,7 @@
import React from "react";
// layouts
-import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
+import { AppLayout } from "layouts/app-layout";
+import { WorkspaceSettingLayout } from "layouts/setting-layout";
// component
import { WorkspaceSettingHeader } from "components/headers";
// ui
@@ -9,24 +10,26 @@ import { Button } from "@plane/ui";
import type { NextPage } from "next";
const BillingSettings: NextPage = () => (
-
}>
-
-
-
+
}>
+
+
-
Current plan
-
You are currently using the free plan
-
-
-
+
+
Billing & Plans
+
-
-
-
+
+
+
Current plan
+
You are currently using the free plan
+
+
+
+
+
+
+
+
);
export default BillingSettings;
diff --git a/web/pages/[workspaceSlug]/settings/exports.tsx b/web/pages/[workspaceSlug]/settings/exports.tsx
index 97e599094..58380df8e 100644
--- a/web/pages/[workspaceSlug]/settings/exports.tsx
+++ b/web/pages/[workspaceSlug]/settings/exports.tsx
@@ -1,20 +1,24 @@
+// layout
+import { AppLayout } from "layouts/app-layout";
+import { WorkspaceSettingLayout } from "layouts/setting-layout";
// components
+import { WorkspaceSettingHeader } from "components/headers";
import ExportGuide from "components/exporter/guide";
// types
import type { NextPage } from "next";
// helper
-import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
-import { WorkspaceSettingHeader } from "components/headers";
const ImportExport: NextPage = () => (
-
}>
-
-
-
+
+
);
export default ImportExport;
diff --git a/web/pages/[workspaceSlug]/settings/imports.tsx b/web/pages/[workspaceSlug]/settings/imports.tsx
index 5263a97b7..033a85848 100644
--- a/web/pages/[workspaceSlug]/settings/imports.tsx
+++ b/web/pages/[workspaceSlug]/settings/imports.tsx
@@ -1,20 +1,23 @@
// layouts
-import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
+import { WorkspaceSettingLayout } from "layouts/setting-layout";
// components
+import { AppLayout } from "layouts/app-layout";
import IntegrationGuide from "components/integration/guide";
import { WorkspaceSettingHeader } from "components/headers";
// types
import type { NextPage } from "next";
const ImportExport: NextPage = () => (
-
}>
-
-
+
}>
+
+
+
+
);
export default ImportExport;
diff --git a/web/pages/[workspaceSlug]/settings/index.tsx b/web/pages/[workspaceSlug]/settings/index.tsx
index 35fb220e8..5ccd4ed9e 100644
--- a/web/pages/[workspaceSlug]/settings/index.tsx
+++ b/web/pages/[workspaceSlug]/settings/index.tsx
@@ -13,7 +13,8 @@ import { FileService } from "services/file.service";
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
// layouts
-import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
+import { AppLayout } from "layouts/app-layout";
+import { WorkspaceSettingLayout } from "layouts/setting-layout";
// components
import { ImageUploadModal } from "components/core";
import { DeleteWorkspaceModal } from "components/workspace";
@@ -152,207 +153,209 @@ const WorkspaceSettings: NextPage = () => {
const isAdmin = memberDetails?.role === 20;
return (
-
}>
-
setIsImageUploadModalOpen(false)}
- isRemoving={isImageRemoving}
- handleDelete={() => handleDelete(activeWorkspace?.logo)}
- onSuccess={(imageUrl) => {
- setIsImageUploading(true);
- setValue("logo", imageUrl);
- setIsImageUploadModalOpen(false);
- handleSubmit(onSubmit)().then(() => setIsImageUploading(false));
- }}
- value={watch("logo")}
- />
- {
- setIsOpen(false);
- }}
- data={activeWorkspace ?? null}
- user={user}
- />
- {activeWorkspace ? (
-
-
-
-
-
-
-
{watch("name")}
-
{`${
- typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
- }/${activeWorkspace.slug}`}
-
-
+
+
+
+
+
+
Workspace Name
+ (
+
+ )}
+ />
+
+
+
+
Company Size
+ (
+ c === value) ?? "Select organization size"}
+ width="w-full"
+ input
+ disabled={!isAdmin}
+ >
+ {ORGANIZATION_SIZE?.map((item) => (
+
+ {item}
+
+ ))}
+
+ )}
+ />
+
+
+
+
Workspace URL
+ (
+
+ )}
+ />
+
+
+
+
+
+ {isSubmitting ? "Updating..." : "Update Workspace"}
+
+
+
+ {isAdmin && (
+
+ {({ open }) => (
+
+
+ Delete Workspace
+ {/* */}
+ {open ? : }
+
+
+
+
+
+
+ The danger zone of the workspace delete page is a critical area that requires careful
+ consideration and attention. When deleting a workspace, all of the data and resources within
+ that workspace will be permanently removed and cannot be recovered.
+
+
+ setIsOpen(true)}>
+ Delete my workspace
+
+
+
+
+
+
+ )}
+
+ )}
+
+ ) : (
+
+
+
+ )}
+
+
);
};
diff --git a/web/pages/[workspaceSlug]/settings/integrations.tsx b/web/pages/[workspaceSlug]/settings/integrations.tsx
index 1ec29736e..434925c82 100644
--- a/web/pages/[workspaceSlug]/settings/integrations.tsx
+++ b/web/pages/[workspaceSlug]/settings/integrations.tsx
@@ -7,7 +7,8 @@ import useSWR from "swr";
// services
import { IntegrationService } from "services/integrations";
// layouts
-import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
+import { AppLayout } from "layouts/app-layout";
+import { WorkspaceSettingLayout } from "layouts/setting-layout";
// components
import { SingleIntegrationCard } from "components/integration";
import { WorkspaceSettingHeader } from "components/headers";
@@ -32,23 +33,25 @@ const WorkspaceIntegrations: NextPage = () => {
);
return (
-
}>
-
-
-
- {appIntegrations ? (
- appIntegrations.map((integration) => (
-
- ))
- ) : (
-
-
-
-
- )}
-
-
-
+
}>
+
+
+
+
+ {appIntegrations ? (
+ appIntegrations.map((integration) => (
+
+ ))
+ ) : (
+
+
+
+
+ )}
+
+
+
+
);
};
diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx
index 0a2cc7cc9..4c055b687 100644
--- a/web/pages/[workspaceSlug]/settings/members.tsx
+++ b/web/pages/[workspaceSlug]/settings/members.tsx
@@ -12,7 +12,8 @@ import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
import useWorkspaceMembers from "hooks/use-workspace-members";
// layouts
-import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
+import { AppLayout } from "layouts/app-layout";
+import { WorkspaceSettingLayout } from "layouts/setting-layout";
// components
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
@@ -92,218 +93,220 @@ const MembersSettings: NextPage = () => {
};
return (
-
}>
-
{
- setSelectedRemoveMember(null);
- setSelectedInviteRemoveMember(null);
- }}
- data={
- selectedRemoveMember
- ? members.find((item) => item.id === selectedRemoveMember)
- : selectedInviteRemoveMember
- ? members.find((item) => item.id === selectedInviteRemoveMember)
- : null
- }
- handleDelete={async () => {
- if (!workspaceSlug) return;
- if (selectedRemoveMember) {
- workspaceService
- .deleteWorkspaceMember(workspaceSlug as string, selectedRemoveMember)
- .catch((err) => {
- const error = err?.error;
- setToastAlert({
- type: "error",
- title: "Error",
- message: error || "Something went wrong",
- });
- })
- .finally(() => {
- mutateMembers((prevData: any) => prevData?.filter((item: any) => item.id !== selectedRemoveMember));
- });
+ }>
+
+ {
+ setSelectedRemoveMember(null);
+ setSelectedInviteRemoveMember(null);
+ }}
+ data={
+ selectedRemoveMember
+ ? members.find((item) => item.id === selectedRemoveMember)
+ : selectedInviteRemoveMember
+ ? members.find((item) => item.id === selectedInviteRemoveMember)
+ : null
}
- if (selectedInviteRemoveMember) {
- mutateInvitations(
- (prevData: any) => prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
- false
- );
- workspaceService
- .deleteWorkspaceInvitations(workspaceSlug as string, selectedInviteRemoveMember)
- .then(() => {
- setToastAlert({
- type: "success",
- title: "Success",
- message: "Member removed successfully",
+ handleDelete={async () => {
+ if (!workspaceSlug) return;
+ if (selectedRemoveMember) {
+ workspaceService
+ .deleteWorkspaceMember(workspaceSlug as string, selectedRemoveMember)
+ .catch((err) => {
+ const error = err?.error;
+ setToastAlert({
+ type: "error",
+ title: "Error",
+ message: error || "Something went wrong",
+ });
+ })
+ .finally(() => {
+ mutateMembers((prevData: any) => prevData?.filter((item: any) => item.id !== selectedRemoveMember));
});
- })
- .catch((err) => {
- const error = err?.error;
- setToastAlert({
- type: "error",
- title: "Error",
- message: error || "Something went wrong",
+ }
+ if (selectedInviteRemoveMember) {
+ mutateInvitations(
+ (prevData: any) => prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
+ false
+ );
+ workspaceService
+ .deleteWorkspaceInvitations(workspaceSlug as string, selectedInviteRemoveMember)
+ .then(() => {
+ setToastAlert({
+ type: "success",
+ title: "Success",
+ message: "Member removed successfully",
+ });
+ })
+ .catch((err) => {
+ const error = err?.error;
+ setToastAlert({
+ type: "error",
+ title: "Error",
+ message: error || "Something went wrong",
+ });
+ })
+ .finally(() => {
+ mutateInvitations();
});
- })
- .finally(() => {
- mutateInvitations();
- });
- }
- setSelectedRemoveMember(null);
- setSelectedInviteRemoveMember(null);
- }}
- />
-
-
-
-
Members
- setInviteModal(true)}>
- Add Member
-
-
- {!workspaceMembers || !workspaceInvitations ? (
-
-
-
-
-
-
- ) : (
-
- {members.length > 0
- ? members.map((member) => (
-
-
- {member.avatar && member.avatar !== "" ? (
-
-
-
-
-
- ) : member.display_name || member.email ? (
-
-
- {(member.display_name || member.email)?.charAt(0)}
-
-
- ) : (
-
- ?
-
- )}
-
- {member.member ? (
+ }
+ setSelectedRemoveMember(null);
+ setSelectedInviteRemoveMember(null);
+ }}
+ />
+
+
+
+
Members
+ setInviteModal(true)}>
+ Add Member
+
+
+ {!workspaceMembers || !workspaceInvitations ? (
+
+
+
+
+
+
+ ) : (
+
+ {members.length > 0
+ ? members.map((member) => (
+
+
+
+ {!member?.status && (
+
+ )}
+ {member?.status && !member?.accountCreated && (
+
+ )}
+
+
+ {ROLE[member.role as keyof typeof ROLE]}
+
+ {member.memberId !== user?.id && }
+
+ }
+ value={member.role}
+ onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
+ if (!workspaceSlug) return;
+
+ mutateMembers(
+ (prevData: any) =>
+ prevData?.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)),
+ false
+ );
+
+ workspaceService
+ .updateWorkspaceMember(workspaceSlug?.toString(), member.id, {
+ role: value,
+ })
+ .catch(() => {
+ setToastAlert({
+ type: "error",
+ title: "Error!",
+ message: "An error occurred while updating member role. Please try again.",
+ });
+ });
+ }}
+ disabled={
+ member.memberId === currentUser?.member.id ||
+ !member.status ||
+ (currentUser && currentUser.role !== 20 && currentUser.role < member.role)
+ }
+ >
+ {Object.keys(ROLE).map((key) => {
+ if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null;
+
+ return (
+
+ <>{ROLE[parseInt(key) as keyof typeof ROLE]}>
+
+ );
+ })}
+
+
+ {
+ if (member.member) {
+ setSelectedRemoveMember(member.id);
+ } else {
+ setSelectedInviteRemoveMember(member.id);
+ }
+ }}
+ >
+
+
+
+ {user?.id === member.memberId ? "Leave" : "Remove member"}
+
+
+
-
- {!member?.status && (
-
- )}
- {member?.status && !member?.accountCreated && (
-
- )}
-
-
- {ROLE[member.role as keyof typeof ROLE]}
-
- {member.memberId !== user?.id && }
-
- }
- value={member.role}
- onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
- if (!workspaceSlug) return;
-
- mutateMembers(
- (prevData: any) =>
- prevData?.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)),
- false
- );
-
- workspaceService
- .updateWorkspaceMember(workspaceSlug?.toString(), member.id, {
- role: value,
- })
- .catch(() => {
- setToastAlert({
- type: "error",
- title: "Error!",
- message: "An error occurred while updating member role. Please try again.",
- });
- });
- }}
- disabled={
- member.memberId === currentUser?.member.id ||
- !member.status ||
- (currentUser && currentUser.role !== 20 && currentUser.role < member.role)
- }
- >
- {Object.keys(ROLE).map((key) => {
- if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null;
-
- return (
-
- <>{ROLE[parseInt(key) as keyof typeof ROLE]}>
-
- );
- })}
-
-
- {
- if (member.member) {
- setSelectedRemoveMember(member.id);
- } else {
- setSelectedInviteRemoveMember(member.id);
- }
- }}
- >
-
-
-
- {user?.id === member.memberId ? "Leave" : "Remove member"}
-
-
-
-
-
- ))
- : null}
-
- )}
-
-
+ ))
+ : null}
+
+ )}
+
+
+
);
};
diff --git a/web/pages/[workspaceSlug]/workspace-views/issues.tsx b/web/pages/[workspaceSlug]/workspace-views/issues.tsx
deleted file mode 100644
index ef81e86a9..000000000
--- a/web/pages/[workspaceSlug]/workspace-views/issues.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-// layouts
-import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
-// components
-// import { WorkspaceIssuesViewOptions } from "components/issues/workspace-views/workspace-issue-view-option";
-// import { WorkspaceViewIssues } from "components/issues/workspace-views/workpace-view-issues";
-// ui
-import { PrimaryButton } from "components/ui";
-// icons
-import { CheckCircle, Plus } from "lucide-react";
-
-const WorkspaceView = () => (
-
-
- Workspace issues
-
- }
- right={
-
- {/*
*/}
-
{
- const e = new KeyboardEvent("keydown", { key: "c" });
- document.dispatchEvent(e);
- }}
- >
-
- Add Issue
-
-
- }
- >
- {/* */}
-
-);
-
-export default WorkspaceView;
From a6d741e7846e0896aeb988087f8f4a24821d1a8a Mon Sep 17 00:00:00 2001
From: guru_sainath
Date: Wed, 25 Oct 2023 16:09:50 +0530
Subject: [PATCH 2/8] chore: implemented drag and drop between dates for
project issues, cycle, module, and project views for calendar layout (#2535)
---
.../issue-layouts/calendar/day-tile.tsx | 61 +++++++------
.../issue-layouts/calendar/issue-blocks.tsx | 58 ++++++------
.../calendar/roots/cycle-root.tsx | 10 ++-
.../calendar/roots/module-root.tsx | 4 +-
.../calendar/roots/project-root.tsx | 10 ++-
.../calendar/roots/project-view-root.tsx | 4 +-
.../cycle/cycle_issue_calendar_view.store.ts | 89 +++++++++++++++++++
web/store/cycle/index.ts | 1 +
web/store/issue/index.ts | 1 +
web/store/issue/issue_calendar_view.store.ts | 88 ++++++++++++++++++
web/store/module/index.ts | 1 +
.../module_issue_calendar_view.store.ts | 89 +++++++++++++++++++
web/store/project-view/index.ts | 2 +
.../project_view_issue_calendar_view.store.ts | 89 +++++++++++++++++++
.../project-view/project_view_issues.store.ts | 32 ++++++-
web/store/root.ts | 16 ++++
16 files changed, 492 insertions(+), 63 deletions(-)
create mode 100644 web/store/cycle/cycle_issue_calendar_view.store.ts
create mode 100644 web/store/issue/issue_calendar_view.store.ts
create mode 100644 web/store/module/module_issue_calendar_view.store.ts
create mode 100644 web/store/project-view/project_view_issue_calendar_view.store.ts
diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx
index 7d07ce7f4..03d797522 100644
--- a/web/components/issues/issue-layouts/calendar/day-tile.tsx
+++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx
@@ -29,35 +29,46 @@ export const CalendarDayTile: React.FC = observer((props) => {
const issuesList = issues ? (issues as IIssueGroupedStructure)[renderDateFormat(date.date)] : null;
return (
-
- {(provided, snapshot) => (
+ <>
+
+ {/* header */}
- <>
-
- {date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "}
- {date.date.getDate()}
-
-
- {provided.placeholder}
- >
+ {date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "}
+ {date.date.getDate()}
- )}
-
+
+ {/* content */}
+
+
+ {(provided, snapshot) => (
+
+
+ {provided.placeholder}
+
+ )}
+
+
+
+ >
);
});
diff --git a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx
index de58b8622..eac2ef43d 100644
--- a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx
+++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx
@@ -17,42 +17,46 @@ export const CalendarIssueBlocks: React.FC = observer((props) => {
const { workspaceSlug } = router.query;
return (
-
)}
))}
-
+ >
);
});
diff --git a/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx b/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx
index 0f0c19f06..ec9e6b94f 100644
--- a/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx
+++ b/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx
@@ -11,12 +11,16 @@ import { IIssueGroupedStructure } from "store/issue";
import { IIssue } from "types";
export const CycleCalendarLayout: React.FC = observer(() => {
- const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore, issueDetail: issueDetailStore } = useMobxStore();
+ const {
+ cycleIssue: cycleIssueStore,
+ issueFilter: issueFilterStore,
+ issueDetail: issueDetailStore,
+ cycleIssueCalendarView: cycleIssueCalendarViewStore,
+ } = useMobxStore();
const router = useRouter();
const { workspaceSlug, cycleId } = router.query;
- // TODO: add drag and drop functionality
const onDragEnd = (result: DropResult) => {
if (!result) return;
@@ -26,7 +30,7 @@ export const CycleCalendarLayout: React.FC = observer(() => {
// return if dropped on the same date
if (result.destination.droppableId === result.source.droppableId) return;
- // issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
+ cycleIssueCalendarViewStore?.handleDragDrop(result.source, result.destination);
};
const issues = cycleIssueStore.getIssues;
diff --git a/web/components/issues/issue-layouts/calendar/roots/module-root.tsx b/web/components/issues/issue-layouts/calendar/roots/module-root.tsx
index c0afd5a0a..1f0219d55 100644
--- a/web/components/issues/issue-layouts/calendar/roots/module-root.tsx
+++ b/web/components/issues/issue-layouts/calendar/roots/module-root.tsx
@@ -15,12 +15,12 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
moduleIssue: moduleIssueStore,
issueFilter: issueFilterStore,
issueDetail: issueDetailStore,
+ moduleIssueCalendarView: moduleIssueCalendarViewStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, moduleId } = router.query;
- // TODO: add drag and drop functionality
const onDragEnd = (result: DropResult) => {
if (!result) return;
@@ -30,7 +30,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
// return if dropped on the same date
if (result.destination.droppableId === result.source.droppableId) return;
- // issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
+ moduleIssueCalendarViewStore?.handleDragDrop(result.source, result.destination);
};
const issues = moduleIssueStore.getIssues;
diff --git a/web/components/issues/issue-layouts/calendar/roots/project-root.tsx b/web/components/issues/issue-layouts/calendar/roots/project-root.tsx
index 1d7c1cea3..96459a350 100644
--- a/web/components/issues/issue-layouts/calendar/roots/project-root.tsx
+++ b/web/components/issues/issue-layouts/calendar/roots/project-root.tsx
@@ -11,12 +11,16 @@ import { IIssueGroupedStructure } from "store/issue";
import { IIssue } from "types";
export const CalendarLayout: React.FC = observer(() => {
- const { issue: issueStore, issueFilter: issueFilterStore, issueDetail: issueDetailStore } = useMobxStore();
+ const {
+ issue: issueStore,
+ issueFilter: issueFilterStore,
+ issueDetail: issueDetailStore,
+ issueCalendarView: issueCalendarViewStore,
+ } = useMobxStore();
const router = useRouter();
const { workspaceSlug } = router.query;
- // TODO: add drag and drop functionality
const onDragEnd = (result: DropResult) => {
if (!result) return;
@@ -26,7 +30,7 @@ export const CalendarLayout: React.FC = observer(() => {
// return if dropped on the same date
if (result.destination.droppableId === result.source.droppableId) return;
- // issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
+ issueCalendarViewStore?.handleDragDrop(result.source, result.destination);
};
const issues = issueStore.getIssues;
diff --git a/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx b/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx
index 5aa9e1545..6ea847609 100644
--- a/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx
+++ b/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx
@@ -15,12 +15,12 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
projectViewIssues: projectViewIssuesStore,
issueFilter: issueFilterStore,
issueDetail: issueDetailStore,
+ projectViewIssueCalendarView: projectViewIssueCalendarViewStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug } = router.query;
- // TODO: add drag and drop functionality
const onDragEnd = (result: DropResult) => {
if (!result) return;
@@ -30,7 +30,7 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
// return if dropped on the same date
if (result.destination.droppableId === result.source.droppableId) return;
- // issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
+ projectViewIssueCalendarViewStore?.handleDragDrop(result.source, result.destination);
};
const issues = projectViewIssuesStore.getIssues;
diff --git a/web/store/cycle/cycle_issue_calendar_view.store.ts b/web/store/cycle/cycle_issue_calendar_view.store.ts
new file mode 100644
index 000000000..0ebb38c37
--- /dev/null
+++ b/web/store/cycle/cycle_issue_calendar_view.store.ts
@@ -0,0 +1,89 @@
+import { action, makeObservable, runInAction } from "mobx";
+// types
+import { RootStore } from "../root";
+import { IIssueType } from "./cycle_issue.store";
+
+export interface ICycleIssueCalendarViewStore {
+ // actions
+ handleDragDrop: (source: any, destination: any) => void;
+}
+
+export class CycleIssueCalendarViewStore implements ICycleIssueCalendarViewStore {
+ // root store
+ rootStore;
+
+ constructor(_rootStore: RootStore) {
+ makeObservable(this, {
+ // actions
+ handleDragDrop: action,
+ });
+
+ this.rootStore = _rootStore;
+ }
+
+ handleDragDrop = async (source: any, destination: any) => {
+ const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
+ const projectId = this.rootStore?.project?.projectId;
+ const cycleId = this.rootStore?.cycle?.cycleId;
+ const issueType: IIssueType | null = this.rootStore?.cycleIssue?.getIssueType;
+ const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
+ const currentIssues: any = this.rootStore.cycleIssue.getIssues;
+
+ if (workspaceSlug && projectId && cycleId && issueType && issueLayout === "calendar" && currentIssues) {
+ // update issue payload
+ let updateIssue: any = {
+ workspaceSlug: workspaceSlug,
+ projectId: projectId,
+ };
+
+ const droppableSourceColumnId = source.droppableId;
+ const droppableDestinationColumnId = destination.droppableId;
+
+ if (droppableSourceColumnId === droppableDestinationColumnId) return;
+
+ if (droppableSourceColumnId != droppableDestinationColumnId) {
+ // horizontal
+ const _sourceIssues = currentIssues[droppableSourceColumnId];
+ let _destinationIssues = currentIssues[droppableDestinationColumnId] || [];
+
+ const [removed] = _sourceIssues.splice(source.index, 1);
+
+ if (_destinationIssues && _destinationIssues.length > 0)
+ _destinationIssues.splice(destination.index, 0, {
+ ...removed,
+ target_date: droppableDestinationColumnId,
+ });
+ else _destinationIssues = [..._destinationIssues, { ...removed, target_date: droppableDestinationColumnId }];
+
+ updateIssue = { ...updateIssue, issueId: removed?.id, target_date: droppableDestinationColumnId };
+
+ currentIssues[droppableSourceColumnId] = _sourceIssues;
+ currentIssues[droppableDestinationColumnId] = _destinationIssues;
+ }
+
+ const reorderedIssues = {
+ ...this.rootStore?.cycleIssue.issues,
+ [cycleId]: {
+ ...this.rootStore?.cycleIssue.issues?.[cycleId],
+ [issueType]: {
+ ...this.rootStore?.cycleIssue.issues?.[cycleId]?.[issueType],
+ [issueType]: currentIssues,
+ },
+ },
+ };
+
+ runInAction(() => {
+ this.rootStore.cycleIssue.issues = { ...reorderedIssues };
+ });
+
+ this.rootStore.issueDetail?.updateIssue(
+ updateIssue.workspaceSlug,
+ updateIssue.projectId,
+ updateIssue.issueId,
+ updateIssue
+ );
+ }
+
+ return;
+ };
+}
diff --git a/web/store/cycle/index.ts b/web/store/cycle/index.ts
index b8fb17576..ae854f978 100644
--- a/web/store/cycle/index.ts
+++ b/web/store/cycle/index.ts
@@ -1,4 +1,5 @@
export * from "./cycle_issue_filters.store";
export * from "./cycle_issue_kanban_view.store";
+export * from "./cycle_issue_calendar_view.store";
export * from "./cycle_issue.store";
export * from "./cycles.store";
diff --git a/web/store/issue/index.ts b/web/store/issue/index.ts
index 27c613eb8..7b6cb4f34 100644
--- a/web/store/issue/index.ts
+++ b/web/store/issue/index.ts
@@ -2,4 +2,5 @@ export * from "./issue_detail.store";
export * from "./issue_draft.store";
export * from "./issue_filters.store";
export * from "./issue_kanban_view.store";
+export * from "./issue_calendar_view.store";
export * from "./issue.store";
diff --git a/web/store/issue/issue_calendar_view.store.ts b/web/store/issue/issue_calendar_view.store.ts
new file mode 100644
index 000000000..5f9cc89bf
--- /dev/null
+++ b/web/store/issue/issue_calendar_view.store.ts
@@ -0,0 +1,88 @@
+import { action, makeObservable, runInAction } from "mobx";
+// types
+import { RootStore } from "../root";
+import { IIssueType } from "./issue.store";
+
+export interface IIssueCalendarViewStore {
+ // actions
+ handleDragDrop: (source: any, destination: any) => void;
+}
+
+export class IssueCalendarViewStore implements IIssueCalendarViewStore {
+ // root store
+ rootStore;
+
+ constructor(_rootStore: RootStore) {
+ makeObservable(this, {
+ // actions
+ handleDragDrop: action,
+ });
+
+ this.rootStore = _rootStore;
+ }
+
+ handleDragDrop = async (source: any, destination: any) => {
+ const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
+ const projectId = this.rootStore?.project?.projectId;
+ const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
+ const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
+ const currentIssues: any = this.rootStore.issue.getIssues;
+
+ if (workspaceSlug && projectId && issueType && issueLayout === "calendar" && currentIssues) {
+ // update issue payload
+ let updateIssue: any = {
+ workspaceSlug: workspaceSlug,
+ projectId: projectId,
+ };
+
+ const droppableSourceColumnId = source.droppableId;
+ const droppableDestinationColumnId = destination.droppableId;
+
+ if (droppableSourceColumnId === droppableDestinationColumnId) return;
+
+ // horizontal
+ if (droppableSourceColumnId != droppableDestinationColumnId) {
+ const _sourceIssues = currentIssues[droppableSourceColumnId];
+ let _destinationIssues = currentIssues[droppableDestinationColumnId] || [];
+
+ const [removed] = _sourceIssues.splice(source.index, 1);
+
+ if (_destinationIssues && _destinationIssues.length > 0)
+ _destinationIssues.splice(destination.index, 0, {
+ ...removed,
+ target_date: droppableDestinationColumnId,
+ });
+ else _destinationIssues = [..._destinationIssues, { ...removed, target_date: droppableDestinationColumnId }];
+
+ updateIssue = { ...updateIssue, issueId: removed?.id, target_date: droppableDestinationColumnId };
+
+ currentIssues[droppableSourceColumnId] = _sourceIssues;
+ currentIssues[droppableDestinationColumnId] = _destinationIssues;
+ }
+
+ const reorderedIssues = {
+ ...this.rootStore?.issue.issues,
+ [projectId]: {
+ ...this.rootStore?.issue.issues?.[projectId],
+ [issueType]: {
+ ...this.rootStore?.issue.issues?.[projectId]?.[issueType],
+ [issueType]: currentIssues,
+ },
+ },
+ };
+
+ runInAction(() => {
+ this.rootStore.issue.issues = { ...reorderedIssues };
+ });
+
+ this.rootStore.issueDetail?.updateIssue(
+ updateIssue.workspaceSlug,
+ updateIssue.projectId,
+ updateIssue.issueId,
+ updateIssue
+ );
+ }
+
+ return;
+ };
+}
diff --git a/web/store/module/index.ts b/web/store/module/index.ts
index 4f2bc1027..3d62d3de8 100644
--- a/web/store/module/index.ts
+++ b/web/store/module/index.ts
@@ -1,4 +1,5 @@
export * from "./module_filters.store";
export * from "./module_issue_kanban_view.store";
+export * from "./module_issue_calendar_view.store";
export * from "./module_issue.store";
export * from "./modules.store";
diff --git a/web/store/module/module_issue_calendar_view.store.ts b/web/store/module/module_issue_calendar_view.store.ts
new file mode 100644
index 000000000..95a866040
--- /dev/null
+++ b/web/store/module/module_issue_calendar_view.store.ts
@@ -0,0 +1,89 @@
+import { action, makeObservable, runInAction } from "mobx";
+// types
+import { RootStore } from "../root";
+import { IIssueType } from "./module_issue.store";
+
+export interface IModuleIssueCalendarViewStore {
+ // actions
+ handleDragDrop: (source: any, destination: any) => void;
+}
+
+export class ModuleIssueCalendarViewStore implements IModuleIssueCalendarViewStore {
+ // root store
+ rootStore;
+
+ constructor(_rootStore: RootStore) {
+ makeObservable(this, {
+ // actions
+ handleDragDrop: action,
+ });
+
+ this.rootStore = _rootStore;
+ }
+
+ handleDragDrop = async (source: any, destination: any) => {
+ const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
+ const projectId = this.rootStore?.project?.projectId;
+ const moduleId = this.rootStore?.module?.moduleId;
+ const issueType: IIssueType | null = this.rootStore?.moduleIssue?.getIssueType;
+ const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
+ const currentIssues: any = this.rootStore.moduleIssue.getIssues;
+
+ if (workspaceSlug && projectId && moduleId && issueType && issueLayout === "calendar" && currentIssues) {
+ // update issue payload
+ let updateIssue: any = {
+ workspaceSlug: workspaceSlug,
+ projectId: projectId,
+ };
+
+ const droppableSourceColumnId = source.droppableId;
+ const droppableDestinationColumnId = destination.droppableId;
+
+ if (droppableSourceColumnId === droppableDestinationColumnId) return;
+
+ if (droppableSourceColumnId != droppableDestinationColumnId) {
+ // horizontal
+ const _sourceIssues = currentIssues[droppableSourceColumnId];
+ let _destinationIssues = currentIssues[droppableDestinationColumnId] || [];
+
+ const [removed] = _sourceIssues.splice(source.index, 1);
+
+ if (_destinationIssues && _destinationIssues.length > 0)
+ _destinationIssues.splice(destination.index, 0, {
+ ...removed,
+ target_date: droppableDestinationColumnId,
+ });
+ else _destinationIssues = [..._destinationIssues, { ...removed, target_date: droppableDestinationColumnId }];
+
+ updateIssue = { ...updateIssue, issueId: removed?.id, target_date: droppableDestinationColumnId };
+
+ currentIssues[droppableSourceColumnId] = _sourceIssues;
+ currentIssues[droppableDestinationColumnId] = _destinationIssues;
+ }
+
+ const reorderedIssues = {
+ ...this.rootStore?.moduleIssue.issues,
+ [moduleId]: {
+ ...this.rootStore?.moduleIssue.issues?.[moduleId],
+ [issueType]: {
+ ...this.rootStore?.moduleIssue.issues?.[moduleId]?.[issueType],
+ [issueType]: currentIssues,
+ },
+ },
+ };
+
+ runInAction(() => {
+ this.rootStore.moduleIssue.issues = { ...reorderedIssues };
+ });
+
+ this.rootStore.issueDetail?.updateIssue(
+ updateIssue.workspaceSlug,
+ updateIssue.projectId,
+ updateIssue.issueId,
+ updateIssue
+ );
+ }
+
+ return;
+ };
+}
diff --git a/web/store/project-view/index.ts b/web/store/project-view/index.ts
index 8fd611bab..39a4c2fac 100644
--- a/web/store/project-view/index.ts
+++ b/web/store/project-view/index.ts
@@ -1,3 +1,5 @@
export * from "./project_view_filters.store";
export * from "./project_view_issues.store";
export * from "./project_views.store";
+
+export * from "./project_view_issue_calendar_view.store";
diff --git a/web/store/project-view/project_view_issue_calendar_view.store.ts b/web/store/project-view/project_view_issue_calendar_view.store.ts
new file mode 100644
index 000000000..2f70df136
--- /dev/null
+++ b/web/store/project-view/project_view_issue_calendar_view.store.ts
@@ -0,0 +1,89 @@
+import { action, makeObservable, runInAction } from "mobx";
+// types
+import { RootStore } from "../root";
+import { IIssueType } from "./project_view_issues.store";
+
+export interface IProjectViewIssueCalendarViewStore {
+ // actions
+ handleDragDrop: (source: any, destination: any) => void;
+}
+
+export class ProjectViewIssueCalendarViewStore implements IProjectViewIssueCalendarViewStore {
+ // root store
+ rootStore;
+
+ constructor(_rootStore: RootStore) {
+ makeObservable(this, {
+ // actions
+ handleDragDrop: action,
+ });
+
+ this.rootStore = _rootStore;
+ }
+
+ handleDragDrop = async (source: any, destination: any) => {
+ const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
+ const projectId = this.rootStore?.project?.projectId;
+ const viewId = this.rootStore?.projectViews?.viewId;
+ const issueType: IIssueType | null = this.rootStore?.projectViewIssues?.getIssueType;
+ const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
+ const currentIssues: any = this.rootStore.projectViewIssues.getIssues;
+
+ if (workspaceSlug && projectId && viewId && issueType && issueLayout === "calendar" && currentIssues) {
+ // update issue payload
+ let updateIssue: any = {
+ workspaceSlug: workspaceSlug,
+ projectId: projectId,
+ };
+
+ const droppableSourceColumnId = source.droppableId;
+ const droppableDestinationColumnId = destination.droppableId;
+
+ if (droppableSourceColumnId === droppableDestinationColumnId) return;
+
+ if (droppableSourceColumnId != droppableDestinationColumnId) {
+ // horizontal
+ const _sourceIssues = currentIssues[droppableSourceColumnId];
+ let _destinationIssues = currentIssues[droppableDestinationColumnId] || [];
+
+ const [removed] = _sourceIssues.splice(source.index, 1);
+
+ if (_destinationIssues && _destinationIssues.length > 0)
+ _destinationIssues.splice(destination.index, 0, {
+ ...removed,
+ target_date: droppableDestinationColumnId,
+ });
+ else _destinationIssues = [..._destinationIssues, { ...removed, target_date: droppableDestinationColumnId }];
+
+ updateIssue = { ...updateIssue, issueId: removed?.id, target_date: droppableDestinationColumnId };
+
+ currentIssues[droppableSourceColumnId] = _sourceIssues;
+ currentIssues[droppableDestinationColumnId] = _destinationIssues;
+ }
+
+ const reorderedIssues = {
+ ...this.rootStore?.projectViewIssues.viewIssues,
+ [viewId]: {
+ ...this.rootStore?.projectViewIssues.viewIssues?.[viewId],
+ [issueType]: {
+ ...this.rootStore?.projectViewIssues.viewIssues?.[viewId]?.[issueType],
+ [issueType]: currentIssues,
+ },
+ },
+ };
+
+ runInAction(() => {
+ this.rootStore.projectViewIssues.viewIssues = { ...reorderedIssues };
+ });
+
+ this.rootStore.issueDetail?.updateIssue(
+ updateIssue.workspaceSlug,
+ updateIssue.projectId,
+ updateIssue.issueId,
+ updateIssue
+ );
+ }
+
+ return;
+ };
+}
diff --git a/web/store/project-view/project_view_issues.store.ts b/web/store/project-view/project_view_issues.store.ts
index 9588237fe..1c6374942 100644
--- a/web/store/project-view/project_view_issues.store.ts
+++ b/web/store/project-view/project_view_issues.store.ts
@@ -6,10 +6,18 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
// types
import { RootStore } from "../root";
-import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store/issue";
import { IIssue, IIssueFilterOptions } from "types";
import { IBlockUpdateData } from "components/gantt-chart";
+export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
+export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
+export type IIssueGroupWithSubGroupsStructure = {
+ [group_id: string]: {
+ [sub_group_id: string]: IIssue[];
+ };
+};
+export type IIssueUnGroupedStructure = IIssue[];
+
export interface IProjectViewIssuesStore {
// states
loader: boolean;
@@ -37,6 +45,7 @@ export interface IProjectViewIssuesStore {
// computed
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
+ getIssueType: IIssueType | null;
}
export class ProjectViewIssuesStore implements IProjectViewIssuesStore {
@@ -75,6 +84,7 @@ export class ProjectViewIssuesStore implements IProjectViewIssuesStore {
fetchViewIssues: action,
// computed
+ getIssueType: computed,
getIssues: computed,
});
@@ -108,6 +118,26 @@ export class ProjectViewIssuesStore implements IProjectViewIssuesStore {
return computedFilters;
};
+ get getIssueType() {
+ const groupedLayouts = ["kanban", "list", "calendar"];
+ const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
+
+ const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
+ const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
+
+ if (!issueLayout) return null;
+
+ const _issueState = groupedLayouts.includes(issueLayout)
+ ? issueSubGroup
+ ? "groupWithSubGroups"
+ : "grouped"
+ : ungroupedLayouts.includes(issueLayout)
+ ? "ungrouped"
+ : null;
+
+ return _issueState || null;
+ }
+
get getIssues() {
const viewId: string | null = this.rootStore.projectViews.viewId;
const issueType = this.rootStore.issue.getIssueType;
diff --git a/web/store/root.ts b/web/store/root.ts
index 4a830435a..1a85185d6 100644
--- a/web/store/root.ts
+++ b/web/store/root.ts
@@ -12,6 +12,8 @@ import {
IssueDetailStore,
IssueFilterStore,
IssueKanBanViewStore,
+ IIssueCalendarViewStore,
+ IssueCalendarViewStore,
IssueStore,
} from "store/issue";
import { IWorkspaceFilterStore, IWorkspaceStore, WorkspaceFilterStore, WorkspaceStore } from "store/workspace";
@@ -24,6 +26,8 @@ import {
ModuleFilterStore,
ModuleIssueKanBanViewStore,
ModuleIssueStore,
+ IModuleIssueCalendarViewStore,
+ ModuleIssueCalendarViewStore,
ModuleStore,
} from "store/module";
import {
@@ -33,6 +37,8 @@ import {
CycleStore,
ICycleIssueFilterStore,
ICycleIssueKanBanViewStore,
+ ICycleIssueCalendarViewStore,
+ CycleIssueCalendarViewStore,
ICycleIssueStore,
ICycleStore,
} from "store/cycle";
@@ -43,6 +49,8 @@ import {
ProjectViewFiltersStore,
ProjectViewIssuesStore,
ProjectViewsStore,
+ IProjectViewIssueCalendarViewStore,
+ ProjectViewIssueCalendarViewStore,
} from "store/project-view";
import CalendarStore, { ICalendarStore } from "store/calendar.store";
import {
@@ -95,19 +103,23 @@ export class RootStore {
moduleIssue: IModuleIssueStore;
moduleFilter: IModuleFilterStore;
moduleIssueKanBanView: IModuleIssueKanBanViewStore;
+ moduleIssueCalendarView: IModuleIssueCalendarViewStore;
cycle: ICycleStore;
cycleIssue: ICycleIssueStore;
cycleIssueFilter: ICycleIssueFilterStore;
cycleIssueKanBanView: ICycleIssueKanBanViewStore;
+ cycleIssueCalendarView: ICycleIssueCalendarViewStore;
projectViews: IProjectViewsStore;
projectViewIssues: IProjectViewIssuesStore;
projectViewFilters: IProjectViewFiltersStore;
+ projectViewIssueCalendarView: IProjectViewIssueCalendarViewStore;
issueFilter: IIssueFilterStore;
issueDetail: IIssueDetailStore;
issueKanBanView: IIssueKanBanViewStore;
+ issueCalendarView: IIssueCalendarViewStore;
draftIssuesStore: DraftIssuesStore;
calendar: ICalendarStore;
@@ -145,20 +157,24 @@ export class RootStore {
this.moduleIssue = new ModuleIssueStore(this);
this.moduleFilter = new ModuleFilterStore(this);
this.moduleIssueKanBanView = new ModuleIssueKanBanViewStore(this);
+ this.moduleIssueCalendarView = new ModuleIssueCalendarViewStore(this);
this.cycle = new CycleStore(this);
this.cycleIssue = new CycleIssueStore(this);
this.cycleIssueFilter = new CycleIssueFilterStore(this);
this.cycleIssueKanBanView = new CycleIssueKanBanViewStore(this);
+ this.cycleIssueCalendarView = new CycleIssueCalendarViewStore(this);
this.projectViews = new ProjectViewsStore(this);
this.projectViewIssues = new ProjectViewIssuesStore(this);
this.projectViewFilters = new ProjectViewFiltersStore(this);
+ this.projectViewIssueCalendarView = new ProjectViewIssueCalendarViewStore(this);
this.issue = new IssueStore(this);
this.issueFilter = new IssueFilterStore(this);
this.issueDetail = new IssueDetailStore(this);
this.issueKanBanView = new IssueKanBanViewStore(this);
+ this.issueCalendarView = new IssueCalendarViewStore(this);
this.draftIssuesStore = new DraftIssuesStore(this);
this.calendar = new CalendarStore(this);
From ca2da41dd2cef3d9d979a506302a55eb3316532e Mon Sep 17 00:00:00 2001
From: guru_sainath
Date: Wed, 25 Oct 2023 19:24:14 +0530
Subject: [PATCH 3/8] chore: issue comment reaction workflow and mutation
update (#2537)
---
.../issue-peek-overview/activity/card.tsx | 3 +
.../activity/comment-card.tsx | 3 +
.../activity/comment-reaction.tsx | 19 +++--
.../issue-peek-overview/activity/view.tsx | 3 +
.../issue-peek-overview/reactions/root.tsx | 2 +-
.../issues/issue-peek-overview/root.tsx | 4 +-
.../issues/issue-peek-overview/view.tsx | 2 +
web/store/issue/issue_detail.store.ts | 73 +++++++++++++------
8 files changed, 75 insertions(+), 34 deletions(-)
diff --git a/web/components/issues/issue-peek-overview/activity/card.tsx b/web/components/issues/issue-peek-overview/activity/card.tsx
index b66b5754f..8fbf8605a 100644
--- a/web/components/issues/issue-peek-overview/activity/card.tsx
+++ b/web/components/issues/issue-peek-overview/activity/card.tsx
@@ -12,6 +12,7 @@ import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/d
interface IssueActivityCard {
workspaceSlug: string;
projectId: string;
+ issueId: string;
user: any;
issueComments: any;
issueCommentUpdate: (comment: any) => void;
@@ -24,6 +25,7 @@ export const IssueActivityCard: FC = (props) => {
const {
workspaceSlug,
projectId,
+ issueId,
user,
issueComments,
issueCommentUpdate,
@@ -118,6 +120,7 @@ export const IssueActivityCard: FC = (props) => {
void;
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
@@ -36,6 +37,7 @@ export const IssueCommentCard: React.FC = (props) => {
showAccessSpecifier = false,
workspaceSlug,
projectId,
+ issueId,
user,
issueCommentReactionCreate,
issueCommentReactionRemove,
@@ -157,6 +159,7 @@ export const IssueCommentCard: React.FC = (props) => {
= observer((props) => {
- const { workspaceSlug, projectId, user, comment, issueCommentReactionCreate, issueCommentReactionRemove } = props;
+ const { workspaceSlug, projectId, issueId, user, comment, issueCommentReactionCreate, issueCommentReactionRemove } =
+ props;
const { issueDetail: issueDetailStore }: RootStore = useMobxStore();
@@ -32,15 +34,18 @@ export const IssueCommentReaction: FC = observer((props)
};
useSWR(
- workspaceSlug && projectId && comment && comment?.id ? `ISSUE+PEEK_OVERVIEW_COMMENT_${comment?.id}` : null,
+ workspaceSlug && projectId && issueId && comment && comment?.id
+ ? `ISSUE+PEEK_OVERVIEW_COMMENT_${comment?.id}`
+ : null,
() => {
- if (workspaceSlug && projectId && comment && comment.id) {
- issueDetailStore.fetchIssueCommentReactions(workspaceSlug, projectId, comment?.id);
+ if (workspaceSlug && projectId && issueId && comment && comment.id) {
+ issueDetailStore.fetchIssueCommentReactions(workspaceSlug, projectId, issueId, comment?.id);
}
}
);
- const issueReactions = issueDetailStore?.getIssueCommentReactionsByCommentId(comment.id) || [];
+ let issueReactions = issueDetailStore?.getIssueCommentReactions || null;
+ issueReactions = issueReactions && comment.id ? issueReactions?.[comment.id] : [];
return (
diff --git a/web/components/issues/issue-peek-overview/activity/view.tsx b/web/components/issues/issue-peek-overview/activity/view.tsx
index f5db0f297..d7f9bcf92 100644
--- a/web/components/issues/issue-peek-overview/activity/view.tsx
+++ b/web/components/issues/issue-peek-overview/activity/view.tsx
@@ -6,6 +6,7 @@ import { IssueCommentEditor } from "./comment-editor";
interface IIssueComment {
workspaceSlug: string;
projectId: string;
+ issueId: string;
user: any;
issueComments: any;
issueCommentCreate: (comment: any) => void;
@@ -19,6 +20,7 @@ export const IssueComment: FC
= (props) => {
const {
workspaceSlug,
projectId,
+ issueId,
user,
issueComments,
issueCommentCreate,
@@ -46,6 +48,7 @@ export const IssueComment: FC = (props) => {
= (props) => {
const handleReaction = (reaction: string) => {
const isReactionAvailable =
- issueReactions[reaction].find((_reaction: any) => _reaction.actor === user?.id) ?? false;
+ issueReactions?.[reaction].find((_reaction: any) => _reaction.actor === user?.id) ?? false;
if (isReactionAvailable) issueReactionRemove(reaction);
else issueReactionCreate(reaction);
diff --git a/web/components/issues/issue-peek-overview/root.tsx b/web/components/issues/issue-peek-overview/root.tsx
index 8b96a3100..e94b728c0 100644
--- a/web/components/issues/issue-peek-overview/root.tsx
+++ b/web/components/issues/issue-peek-overview/root.tsx
@@ -50,10 +50,10 @@ export const IssuePeekOverview: FC = observer((props) => {
issueDetailStore.removeIssueComment(workspaceSlug, projectId, issueId, commentId);
const issueCommentReactionCreate = (commentId: string, reaction: string) =>
- issueDetailStore.creationIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
+ issueDetailStore.creationIssueCommentReaction(workspaceSlug, projectId, issueId, commentId, reaction);
const issueCommentReactionRemove = (commentId: string, reaction: string) =>
- issueDetailStore.removeIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
+ issueDetailStore.removeIssueCommentReaction(workspaceSlug, projectId, issueId, commentId, reaction);
return (
= observer((props) => {
= observer((props) => {
void;
@@ -31,7 +33,7 @@ export interface IIssueDetailStore {
getIssue: IIssue | null;
getIssueReactions: any | null;
getIssueComments: any | null;
- getIssueCommentReactionsByCommentId: any | null;
+ getIssueCommentReactions: any | null;
// fetch issue details
fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise;
@@ -59,16 +61,23 @@ export interface IIssueDetailStore {
) => Promise;
removeIssueComment: (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => Promise;
- fetchIssueCommentReactions: (workspaceSlug: string, projectId: string, commentId: string) => Promise;
+ fetchIssueCommentReactions: (
+ workspaceSlug: string,
+ projectId: string,
+ issueId: string,
+ commentId: string
+ ) => Promise;
creationIssueCommentReaction: (
workspaceSlug: string,
projectId: string,
+ issueId: string,
commentId: string,
reaction: string
) => Promise;
removeIssueCommentReaction: (
workspaceSlug: string,
projectId: string,
+ issueId: string,
commentId: string,
reaction: string
) => Promise;
@@ -114,11 +123,10 @@ export class IssueDetailStore implements IIssueDetailStore {
getIssue: computed,
getIssueReactions: computed,
getIssueComments: computed,
+ getIssueCommentReactions: computed,
setPeekId: action,
- getIssueCommentReactionsByCommentId: action,
-
fetchIssueDetails: action,
createIssue: action,
updateIssue: action,
@@ -164,11 +172,11 @@ export class IssueDetailStore implements IIssueDetailStore {
return _comments || null;
}
- getIssueCommentReactionsByCommentId = (commentId: string) => {
- if (!commentId) return null;
- const _reactions = this.issue_comment_reactions[commentId];
- return _reactions || null;
- };
+ get getIssueCommentReactions() {
+ if (!this.peekId) return null;
+ const _commentReactions = this.issue_comment_reactions[this.peekId];
+ return _commentReactions || null;
+ }
setPeekId = (issueId: string | null) => (this.peekId = issueId);
@@ -513,13 +521,16 @@ export class IssueDetailStore implements IIssueDetailStore {
};
// comment reaction
- fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, commentId: string) => {
+ fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
try {
const _reactions = await this.issueReactionService.listIssueCommentReactions(workspaceSlug, projectId, commentId);
const _issue_comment_reactions = {
...this.issue_comment_reactions,
- [commentId]: groupReactionEmojis(_reactions),
+ [issueId]: {
+ ...this.issue_comment_reactions[issueId],
+ [commentId]: groupReactionEmojis(_reactions),
+ },
};
runInAction(() => {
@@ -533,10 +544,12 @@ export class IssueDetailStore implements IIssueDetailStore {
creationIssueCommentReaction = async (
workspaceSlug: string,
projectId: string,
+ issueId: string,
commentId: string,
reaction: string
) => {
- let _currentReactions = this.getIssueCommentReactionsByCommentId(commentId);
+ let _currentReactions = this.getIssueCommentReactions;
+ _currentReactions = _currentReactions && commentId ? _currentReactions?.[commentId] : null;
try {
const _reaction = await this.issueReactionService.createIssueCommentReaction(
@@ -550,14 +563,19 @@ export class IssueDetailStore implements IIssueDetailStore {
_currentReactions = {
..._currentReactions,
- [reaction]: [..._currentReactions[reaction], { ..._reaction }],
+ [reaction]: [..._currentReactions?.[reaction], { ..._reaction }],
+ };
+
+ const _issue_comment_reactions = {
+ ...this.issue_comment_reactions,
+ [issueId]: {
+ ...this.issue_comment_reactions[issueId],
+ [commentId]: _currentReactions,
+ },
};
runInAction(() => {
- this.issue_comment_reactions = {
- ...this.issue_comment_reactions,
- [commentId]: _currentReactions,
- };
+ this.issue_comment_reactions = _issue_comment_reactions;
});
} catch (error) {
console.warn("error removing the issue comment", error);
@@ -567,10 +585,12 @@ export class IssueDetailStore implements IIssueDetailStore {
removeIssueCommentReaction = async (
workspaceSlug: string,
projectId: string,
+ issueId: string,
commentId: string,
reaction: string
) => {
- let _currentReactions = this.getIssueCommentReactionsByCommentId(commentId);
+ let _currentReactions = this.getIssueCommentReactions;
+ _currentReactions = _currentReactions && commentId ? _currentReactions?.[commentId] : null;
try {
const user = this.rootStore.user.currentUser;
@@ -578,14 +598,19 @@ export class IssueDetailStore implements IIssueDetailStore {
if (user) {
_currentReactions = {
..._currentReactions,
- [reaction]: [..._currentReactions[reaction].filter((r: any) => r.actor !== user.id)],
+ [reaction]: [..._currentReactions?.[reaction].filter((r: any) => r.actor !== user.id)],
+ };
+
+ const _issue_comment_reactions = {
+ ...this.issue_comment_reactions,
+ [issueId]: {
+ ...this.issue_comment_reactions[issueId],
+ [commentId]: _currentReactions,
+ },
};
runInAction(() => {
- this.issue_comment_reactions = {
- ...this.issue_comment_reactions,
- [commentId]: _currentReactions,
- };
+ this.issue_comment_reactions = _issue_comment_reactions;
});
await this.issueReactionService.deleteIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
From a49f00bd39eb463a361926cdcb84dc7eed049e74 Mon Sep 17 00:00:00 2001
From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Date: Wed, 25 Oct 2023 19:47:58 +0530
Subject: [PATCH 4/8] chore: refactor and beautify issue properties (#2539)
* chore: update all issue property components
* style: issue properties
---
web/components/estimates/estimate-select.tsx | 160 ++++++++
.../estimates/{index.tsx => index.ts} | 3 +-
.../issues/issue-layouts/kanban/block.tsx | 27 +-
.../issue-layouts/kanban/blocks-list.tsx | 24 +-
.../issues/issue-layouts/kanban/default.tsx | 57 ++-
.../issues/issue-layouts/kanban/index.ts | 4 +-
.../issue-layouts/kanban/properties.tsx | 362 +++++++++---------
.../kanban/{ => roots}/cycle-root.tsx | 31 +-
.../issue-layouts/kanban/roots/index.ts | 5 +
.../kanban/{ => roots}/module-root.tsx | 25 +-
.../{ => roots}/profile-issues-root.tsx | 13 +-
.../{root.tsx => roots/project-root.tsx} | 29 +-
.../project-view-root.tsx} | 6 +-
.../issues/issue-layouts/kanban/swimlanes.tsx | 57 ++-
.../issues/issue-layouts/list/block.tsx | 17 +-
.../issues/issue-layouts/list/blocks-list.tsx | 14 +-
.../issues/issue-layouts/list/default.tsx | 28 +-
.../issues/issue-layouts/list/index.ts | 4 +-
.../issues/issue-layouts/list/properties.tsx | 103 +++--
.../list/{ => roots}/cycle-root.tsx | 19 +-
.../issues/issue-layouts/list/roots/index.ts | 5 +
.../list/{ => roots}/module-root.tsx | 19 +-
.../list/{ => roots}/profile-issues-root.tsx | 7 +-
.../list/{root.tsx => roots/project-root.tsx} | 19 +-
.../project-view-root.tsx} | 4 +-
.../issue-layouts/properties/assignee.tsx | 256 +------------
.../issues/issue-layouts/properties/date.tsx | 140 ++++---
.../issue-layouts/properties/estimates.tsx | 229 +----------
.../issue-layouts/properties/labels.tsx | 234 +----------
.../issue-layouts/properties/priority.tsx | 226 +----------
.../issues/issue-layouts/properties/state.tsx | 214 +----------
.../spreadsheet/columns/state-column.tsx | 6 +-
.../issues/issue-peek-overview/properties.tsx | 38 +-
.../issues/sub-issues/properties.tsx | 6 +-
web/components/project/label-select.tsx | 270 ++++++-------
web/components/project/members-select.tsx | 27 +-
web/components/project/priority-select.tsx | 80 ++--
web/components/states/state-select.tsx | 33 +-
web/layouts/auth-layout/project-wrapper.tsx | 8 +-
.../profile/[userId]/assigned.tsx | 4 +-
40 files changed, 1066 insertions(+), 1747 deletions(-)
create mode 100644 web/components/estimates/estimate-select.tsx
rename web/components/estimates/{index.tsx => index.ts} (77%)
rename web/components/issues/issue-layouts/kanban/{ => roots}/cycle-root.tsx (83%)
create mode 100644 web/components/issues/issue-layouts/kanban/roots/index.ts
rename web/components/issues/issue-layouts/kanban/{ => roots}/module-root.tsx (85%)
rename web/components/issues/issue-layouts/kanban/{ => roots}/profile-issues-root.tsx (94%)
rename web/components/issues/issue-layouts/kanban/{root.tsx => roots/project-root.tsx} (82%)
rename web/components/issues/issue-layouts/kanban/{view-root.tsx => roots/project-view-root.tsx} (95%)
rename web/components/issues/issue-layouts/list/{ => roots}/cycle-root.tsx (80%)
create mode 100644 web/components/issues/issue-layouts/list/roots/index.ts
rename web/components/issues/issue-layouts/list/{ => roots}/module-root.tsx (80%)
rename web/components/issues/issue-layouts/list/{ => roots}/profile-issues-root.tsx (95%)
rename web/components/issues/issue-layouts/list/{root.tsx => roots/project-root.tsx} (77%)
rename web/components/issues/issue-layouts/list/{view-root.tsx => roots/project-view-root.tsx} (94%)
diff --git a/web/components/estimates/estimate-select.tsx b/web/components/estimates/estimate-select.tsx
new file mode 100644
index 000000000..5ac283b83
--- /dev/null
+++ b/web/components/estimates/estimate-select.tsx
@@ -0,0 +1,160 @@
+import React, { useState } from "react";
+import { usePopper } from "react-popper";
+import { Combobox } from "@headlessui/react";
+import { Check, ChevronDown, Search, Triangle } from "lucide-react";
+// types
+import { Tooltip } from "components/ui";
+import { Placement } from "@popperjs/core";
+// constants
+import { IEstimatePoint } from "types";
+
+type Props = {
+ value: number | null;
+ onChange: (value: number | null) => void;
+ estimatePoints: IEstimatePoint[] | undefined;
+ className?: string;
+ buttonClassName?: string;
+ optionsClassName?: string;
+ placement?: Placement;
+ hideDropdownArrow?: boolean;
+ disabled?: boolean;
+};
+
+export const EstimateSelect: React.FC = (props) => {
+ const {
+ value,
+ onChange,
+ estimatePoints,
+ className = "",
+ buttonClassName = "",
+ optionsClassName = "",
+ placement,
+ hideDropdownArrow = false,
+ disabled = false,
+ } = props;
+
+ const [query, setQuery] = useState("");
+
+ const [referenceElement, setReferenceElement] = useState(null);
+ const [popperElement, setPopperElement] = useState(null);
+
+ const { styles, attributes } = usePopper(referenceElement, popperElement, {
+ placement: placement ?? "bottom-start",
+ modifiers: [
+ {
+ name: "preventOverflow",
+ options: {
+ padding: 12,
+ },
+ },
+ ],
+ });
+
+ const options: { value: number | null; query: string; content: any }[] | undefined = estimatePoints?.map(
+ (estimate) => ({
+ value: estimate.key,
+ query: estimate.value,
+ content: (
+
+
+ {estimate.value}
+
+ ),
+ })
+ );
+ options?.unshift({
+ value: null,
+ query: "none",
+ content: (
+
+
+ None
+
+ ),
+ });
+
+ const filteredOptions =
+ query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
+
+ const selectedEstimate = estimatePoints?.find((e) => e.key === value);
+ const label = (
+
+
+
+ {selectedEstimate?.value ?? "None"}
+
+
+ );
+
+ return (
+ onChange(val as number | null)}
+ disabled={disabled}
+ >
+
+
+ {label}
+ {!hideDropdownArrow && !disabled && }
+
+
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder="Search"
+ displayValue={(assigned: any) => assigned?.name}
+ />
+
+
+ {filteredOptions ? (
+ filteredOptions.length > 0 ? (
+ filteredOptions.map((option) => (
+
+ `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
+ active ? "bg-custom-background-80" : ""
+ } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
+ }
+ >
+ {({ selected }) => (
+ <>
+ {option.content}
+ {selected && }
+ >
+ )}
+
+ ))
+ ) : (
+
+ No matching results
+
+ )
+ ) : (
+
Loading...
+ )}
+
+
+
+
+ );
+};
diff --git a/web/components/estimates/index.tsx b/web/components/estimates/index.ts
similarity index 77%
rename from web/components/estimates/index.tsx
rename to web/components/estimates/index.ts
index f20c74780..b88ceaf03 100644
--- a/web/components/estimates/index.tsx
+++ b/web/components/estimates/index.ts
@@ -1,3 +1,4 @@
export * from "./create-update-estimate-modal";
-export * from "./single-estimate";
export * from "./delete-estimate-modal";
+export * from "./estimate-select";
+export * from "./single-estimate";
diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx
index 34f0a550e..505da1da3 100644
--- a/web/components/issues/issue-layouts/kanban/block.tsx
+++ b/web/components/issues/issue-layouts/kanban/block.tsx
@@ -2,7 +2,7 @@ import { Draggable } from "@hello-pangea/dnd";
// components
import { KanBanProperties } from "./properties";
// types
-import { IIssue } from "types";
+import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite } from "types";
interface IssueBlockProps {
sub_group_id: string;
@@ -18,10 +18,27 @@ interface IssueBlockProps {
) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
displayProperties: any;
+ states: IState[] | null;
+ labels: IIssueLabels[] | null;
+ members: IUserLite[] | null;
+ estimates: IEstimatePoint[] | null;
}
export const KanbanIssueBlock: React.FC = (props) => {
- const { sub_group_id, columnId, index, issue, isDragDisabled, handleIssues, quickActions, displayProperties } = props;
+ const {
+ sub_group_id,
+ columnId,
+ index,
+ issue,
+ isDragDisabled,
+ handleIssues,
+ quickActions,
+ displayProperties,
+ states,
+ labels,
+ members,
+ estimates,
+ } = props;
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, "update");
@@ -54,7 +71,7 @@ export const KanbanIssueBlock: React.FC = (props) => {
{issue.project_detail.identifier}-{issue.sequence_id}
)}
- {issue.name}
+ {issue.name}
= (props) => {
issue={issue}
handleIssues={updateIssue}
display_properties={displayProperties}
+ states={states}
+ labels={labels}
+ members={members}
+ estimates={estimates}
/>
diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx
index aeee5c2fc..0e921638a 100644
--- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx
+++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx
@@ -1,6 +1,6 @@
// components
import { KanbanIssueBlock } from "components/issues";
-import { IIssue } from "types";
+import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite } from "types";
interface IssueBlocksListProps {
sub_group_id: string;
@@ -15,10 +15,26 @@ interface IssueBlocksListProps {
) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
display_properties: any;
+ states: IState[] | null;
+ labels: IIssueLabels[] | null;
+ members: IUserLite[] | null;
+ estimates: IEstimatePoint[] | null;
}
export const KanbanIssueBlocksList: React.FC