[WEB-626] chore: fix sentry issues and refactor issue actions logic for issue layouts (#3650)

* restructure the logic to avoid throwing error if any dat is not found

* updated files for previous commit

* fix build errors

* remove throwing error if userId is undefined

* optionally chain display_name property to fix sentry issues

* add ooptional check

* change issue action logic to increase code maintainability and make sure to send only the updated date while updating the issue

* fix issue updation bugs

* fix module issues build error

* fix runtime errors
This commit is contained in:
rahulramesha 2024-03-06 20:47:38 +05:30 committed by GitHub
parent a852e3cc52
commit c16a5b9b71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
98 changed files with 1402 additions and 1864 deletions

View File

@ -11,7 +11,6 @@ import { FullScreenPeekView, SidePeekView } from "components/issues/peek-overvie
// lib // lib
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
export const IssuePeekOverview: React.FC = observer(() => { export const IssuePeekOverview: React.FC = observer(() => {
// states // states
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false); const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);

View File

@ -24,7 +24,7 @@ export const AnalyticsLeaderBoard: React.FC<Props> = ({ users, title, emptyState
<div className="mt-3 space-y-3"> <div className="mt-3 space-y-3">
{users.map((user) => ( {users.map((user) => (
<a <a
key={user.display_name ?? "None"} key={user?.display_name ?? "None"}
href={`/${workspaceSlug}/profile/${user.id}`} href={`/${workspaceSlug}/profile/${user.id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@ -36,16 +36,16 @@ export const AnalyticsLeaderBoard: React.FC<Props> = ({ users, title, emptyState
<img <img
src={user.avatar} src={user.avatar}
className="absolute left-0 top-0 h-full w-full rounded-full object-cover" className="absolute left-0 top-0 h-full w-full rounded-full object-cover"
alt={user.display_name ?? "None"} alt={user?.display_name ?? "None"}
/> />
</div> </div>
) : ( ) : (
<div className="grid h-4 w-4 flex-shrink-0 place-items-center rounded-full bg-gray-700 text-[11px] capitalize text-white"> <div className="grid h-4 w-4 flex-shrink-0 place-items-center rounded-full bg-gray-700 text-[11px] capitalize text-white">
{user.display_name !== "" ? user?.display_name?.[0] : "?"} {user?.display_name !== "" ? user?.display_name?.[0] : "?"}
</div> </div>
)} )}
<span className="break-words text-custom-text-200"> <span className="break-words text-custom-text-200">
{user.display_name !== "" ? `${user.display_name}` : "No assignee"} {user?.display_name !== "" ? `${user?.display_name}` : "No assignee"}
</span> </span>
</div> </div>
<span className="flex-shrink-0">{user.count}</span> <span className="flex-shrink-0">{user.count}</span>

View File

@ -137,8 +137,8 @@ export const SidebarProgressStats: React.FC<Props> = ({
key={assignee.assignee_id} key={assignee.assignee_id}
title={ title={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Avatar name={assignee.display_name ?? undefined} src={assignee?.avatar ?? undefined} /> <Avatar name={assignee?.display_name ?? undefined} src={assignee?.avatar ?? undefined} />
<span>{assignee.display_name}</span> <span>{assignee?.display_name ?? ""}</span>
</div> </div>
} }
completed={assignee.completed_issues} completed={assignee.completed_issues}

View File

@ -82,7 +82,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Avatar name={assignee?.display_name ?? undefined} src={assignee?.avatar ?? undefined} /> <Avatar name={assignee?.display_name ?? undefined} src={assignee?.avatar ?? undefined} />
<span>{assignee.display_name}</span> <span>{assignee?.display_name ?? ""}</span>
</div> </div>
} }
completed={assignee.completed_issues} completed={assignee.completed_issues}

View File

@ -21,11 +21,7 @@ export const CycleMobileHeader = () => {
{ key: "calendar", title: "Calendar", icon: Calendar }, { key: "calendar", title: "Calendar", icon: Calendar },
]; ];
const { workspaceSlug, projectId, cycleId } = router.query as { const { workspaceSlug, projectId, cycleId } = router.query;
workspaceSlug: string;
projectId: string;
cycleId: string;
};
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined; const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
// store hooks // store hooks
const { const {
@ -35,8 +31,14 @@ export const CycleMobileHeader = () => {
const handleLayoutChange = useCallback( const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => { (layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !cycleId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, cycleId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
cycleId.toString()
);
}, },
[workspaceSlug, projectId, cycleId, updateFilters] [workspaceSlug, projectId, cycleId, updateFilters]
); );
@ -49,7 +51,7 @@ export const CycleMobileHeader = () => {
const handleFiltersUpdate = useCallback( const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => { (key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !cycleId) return;
const newValues = issueFilters?.filters?.[key] ?? []; const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -61,23 +63,41 @@ export const CycleMobileHeader = () => {
else newValues.push(value); else newValues.push(value);
} }
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, cycleId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ [key]: newValues },
cycleId.toString()
);
}, },
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters] [workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
); );
const handleDisplayFilters = useCallback( const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !cycleId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, cycleId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
cycleId.toString()
);
}, },
[workspaceSlug, projectId, cycleId, updateFilters] [workspaceSlug, projectId, cycleId, updateFilters]
); );
const handleDisplayProperties = useCallback( const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => { (property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !cycleId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, cycleId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
cycleId.toString()
);
}, },
[workspaceSlug, projectId, cycleId, updateFilters] [workspaceSlug, projectId, cycleId, updateFilters]
); );

View File

@ -71,7 +71,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
<div className="-mt-1 break-words"> <div className="-mt-1 break-words">
<p className="text-sm text-custom-text-200"> <p className="text-sm text-custom-text-200">
<span className="font-medium text-custom-text-100"> <span className="font-medium text-custom-text-100">
{currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail.display_name}{" "} {currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail?.display_name}{" "}
</span> </span>
{activity.field ? ( {activity.field ? (
<ActivityMessage activity={activity} showIssue /> <ActivityMessage activity={activity} showIssue />

View File

@ -100,14 +100,14 @@ export const CollaboratorsList: React.FC<CollaboratorsListProps> = (props) => {
updateIsLoading?.(false); updateIsLoading?.(false);
updateTotalPages(widgetStats.total_pages); updateTotalPages(widgetStats.total_pages);
updateResultsCount(widgetStats.results.length); updateResultsCount(widgetStats.results?.length);
}, [updateIsLoading, updateResultsCount, updateTotalPages, widgetStats]); }, [updateIsLoading, updateResultsCount, updateTotalPages, widgetStats]);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />; if (!widgetStats || !widgetStats?.results) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return ( return (
<> <>
{widgetStats?.results.map((user) => ( {widgetStats?.results?.map((user) => (
<CollaboratorListItem <CollaboratorListItem
key={user.user_id} key={user.user_id}
issueCount={user.active_issue_count} issueCount={user.active_issue_count}

View File

@ -25,6 +25,7 @@ import {
useUser, useUser,
useIssues, useIssues,
} from "hooks/store"; } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// components // components
// ui // ui
@ -67,8 +68,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
// store hooks // store hooks
const { const {
issuesFilter: { issueFilters, updateFilters }, issuesFilter: { issueFilters },
} = useIssues(EIssuesStoreType.MODULE); } = useIssues(EIssuesStoreType.MODULE);
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
const { projectModuleIds, getModuleById } = useModule(); const { projectModuleIds, getModuleById } = useModule();
const { const {
commandPalette: { toggleCreateIssueModal }, commandPalette: { toggleCreateIssueModal },
@ -95,21 +97,15 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
const handleLayoutChange = useCallback( const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => { (layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return; if (!projectId) return;
updateFilters( updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
moduleId?.toString()
);
}, },
[workspaceSlug, projectId, moduleId, updateFilters] [projectId, moduleId, updateFilters]
); );
const handleFiltersUpdate = useCallback( const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => { (key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return; if (!projectId) return;
const newValues = issueFilters?.filters?.[key] ?? []; const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -121,43 +117,25 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
else newValues.push(value); else newValues.push(value);
} }
updateFilters( updateFilters(projectId.toString(), EIssueFilterType.FILTERS, { [key]: newValues });
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ [key]: newValues },
moduleId?.toString()
);
}, },
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters] [projectId, moduleId, issueFilters, updateFilters]
); );
const handleDisplayFilters = useCallback( const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return; if (!projectId) return;
updateFilters( updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
moduleId?.toString()
);
}, },
[workspaceSlug, projectId, moduleId, updateFilters] [projectId, moduleId, updateFilters]
); );
const handleDisplayProperties = useCallback( const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => { (property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return; if (!projectId) return;
updateFilters( updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property);
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
moduleId?.toString()
);
}, },
[workspaceSlug, projectId, moduleId, updateFilters] [projectId, moduleId, updateFilters]
); );
// derived values // derived values

View File

@ -33,11 +33,7 @@ import { ProjectLogo } from "components/project";
export const ProjectViewIssuesHeader: React.FC = observer(() => { export const ProjectViewIssuesHeader: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query as { const { workspaceSlug, projectId, viewId } = router.query;
workspaceSlug: string;
projectId: string;
viewId: string;
};
// store hooks // store hooks
const { const {
issuesFilter: { issueFilters, updateFilters }, issuesFilter: { issueFilters, updateFilters },
@ -61,15 +57,21 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
const handleLayoutChange = useCallback( const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => { (layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, viewId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
viewId.toString()
);
}, },
[workspaceSlug, projectId, viewId, updateFilters] [workspaceSlug, projectId, viewId, updateFilters]
); );
const handleFiltersUpdate = useCallback( const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => { (key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !viewId) return;
const newValues = issueFilters?.filters?.[key] ?? []; const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -81,23 +83,41 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
else newValues.push(value); else newValues.push(value);
} }
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, viewId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ [key]: newValues },
viewId.toString()
);
}, },
[workspaceSlug, projectId, viewId, issueFilters, updateFilters] [workspaceSlug, projectId, viewId, issueFilters, updateFilters]
); );
const handleDisplayFilters = useCallback( const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, viewId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
viewId.toString()
);
}, },
[workspaceSlug, projectId, viewId, updateFilters] [workspaceSlug, projectId, viewId, updateFilters]
); );
const handleDisplayProperties = useCallback( const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => { (property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, viewId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
viewId.toString()
);
}, },
[workspaceSlug, projectId, viewId, updateFilters] [workspaceSlug, projectId, viewId, updateFilters]
); );

View File

@ -44,16 +44,27 @@ export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users,
workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug.toString()) : null workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug.toString()) : null
); );
const options = members?.map((member) => ({ const options = members
value: member.member.display_name, ?.map((member) => {
query: member.member.display_name ?? "", if (!member?.member) return;
content: ( return {
<div className="flex items-center gap-2"> value: member.member?.display_name,
<Avatar name={member?.member.display_name} src={member?.member.avatar} /> query: member.member?.display_name ?? "",
{member.member.display_name} content: (
</div> <div className="flex items-center gap-2">
), <Avatar name={member?.member?.display_name} src={member?.member?.avatar} />
})); {member.member?.display_name}
</div>
),
};
})
.filter((member) => !!member) as
| {
value: string;
query: string;
content: JSX.Element;
}[]
| undefined;
return ( return (
<div className="grid grid-cols-3 items-center gap-2 rounded-md bg-custom-background-80 px-2 py-3"> <div className="grid grid-cols-3 items-center gap-2 rounded-md bg-custom-background-80 px-2 py-3">

View File

@ -33,16 +33,27 @@ export const JiraImportUsers: FC = () => {
workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug?.toString() ?? "") : null workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug?.toString() ?? "") : null
); );
const options = members?.map((member) => ({ const options = members
value: member.member.email, ?.map((member) => {
query: member.member.display_name ?? "", if (!member?.member) return;
content: ( return {
<div className="flex items-center gap-2"> value: member.member.email,
<Avatar name={member?.member.display_name} src={member?.member.avatar} /> query: member.member.display_name ?? "",
{member.member.display_name} content: (
</div> <div className="flex items-center gap-2">
), <Avatar name={member?.member.display_name} src={member?.member.avatar} />
})); {member.member.display_name}
</div>
),
};
})
.filter((member) => !!member) as
| {
value: string;
query: string;
content: JSX.Element;
}[]
| undefined;
return ( return (
<div className="h-full w-full space-y-10 divide-y-2 divide-custom-border-200 overflow-y-auto"> <div className="h-full w-full space-y-10 divide-y-2 divide-custom-border-200 overflow-y-auto">

View File

@ -40,7 +40,7 @@ export const SingleImport: React.FC<Props> = ({ service, refreshing, handleDelet
</h4> </h4>
<div className="mt-2 flex items-center gap-2 text-xs text-custom-text-200"> <div className="mt-2 flex items-center gap-2 text-xs text-custom-text-200">
<span>{renderFormattedDate(service.created_at)}</span>| <span>{renderFormattedDate(service.created_at)}</span>|
<span>Imported by {service.initiated_by_detail.display_name}</span> <span>Imported by {service.initiated_by_detail?.display_name}</span>
</div> </div>
</div> </div>
<CustomMenu ellipsis> <CustomMenu ellipsis>

View File

@ -217,10 +217,10 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
message: () => "Cycle remove from issue failed", message: () => "Cycle remove from issue failed",
}, },
}); });
const response = await removeFromCyclePromise; await removeFromCyclePromise;
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_UPDATED, eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue detail page" }, payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
updates: { updates: {
changed_property: "cycle_id", changed_property: "cycle_id",
change_details: "", change_details: "",

View File

@ -1,34 +1,30 @@
import { FC, useCallback } from "react"; import { FC } from "react";
import { DragDropContext, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, DropResult } from "@hello-pangea/dnd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// components // components
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
import { CalendarChart } from "components/issues"; import { CalendarChart } from "components/issues";
// hooks
import { useIssues, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// ui // ui
// types // types
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle"; import { TGroupedIssues } from "@plane/types";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module"; import { EIssuesStoreType } from "constants/issue";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
import { TGroupedIssues, TIssue } from "@plane/types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EIssueActions } from "../types";
import { handleDragDrop } from "./utils"; import { handleDragDrop } from "./utils";
import { useIssues, useUser } from "hooks/store";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
type CalendarStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW;
interface IBaseCalendarRoot { interface IBaseCalendarRoot {
issueStore: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues;
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: { storeType: CalendarStoreType;
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.RESTORE]?: (issue: TIssue) => Promise<void>;
};
addIssuesToView?: (issueIds: string[]) => Promise<any>; addIssuesToView?: (issueIds: string[]) => Promise<any>;
viewId?: string; viewId?: string;
isCompletedCycle?: boolean; isCompletedCycle?: boolean;
@ -36,10 +32,8 @@ interface IBaseCalendarRoot {
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { const {
issueStore,
issuesFilterStore,
QuickActions, QuickActions,
issueActions, storeType,
addIssuesToView, addIssuesToView,
viewId, viewId,
isCompletedCycle = false, isCompletedCycle = false,
@ -50,16 +44,18 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// hooks // hooks
const { issueMap } = useIssues();
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { issues, issuesFilter, issueMap } = useIssues(storeType);
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
useIssuesActions(storeType);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
const displayFilters = issuesFilterStore.issueFilters?.displayFilters; const displayFilters = issuesFilter.issueFilters?.displayFilters;
const groupedIssueIds = (issueStore.groupedIssueIds ?? {}) as TGroupedIssues; const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
const onDragEnd = async (result: DropResult) => { const onDragEnd = async (result: DropResult) => {
if (!result) return; if (!result) return;
@ -76,10 +72,9 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
result.destination, result.destination,
workspaceSlug?.toString(), workspaceSlug?.toString(),
projectId?.toString(), projectId?.toString(),
issueStore,
issueMap, issueMap,
groupedIssueIds, groupedIssueIds,
viewId updateIssue
).catch((err) => { ).catch((err) => {
setToast({ setToast({
title: "Error", title: "Error",
@ -90,21 +85,12 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
} }
}; };
const handleIssues = useCallback(
async (date: string, issue: TIssue, action: EIssueActions) => {
if (issueActions[action]) {
await issueActions[action]!(issue);
}
},
[issueActions]
);
return ( return (
<> <>
<div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4"> <div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4">
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<CalendarChart <CalendarChart
issuesFilterStore={issuesFilterStore} issuesFilterStore={issuesFilter}
issues={issueMap} issues={issueMap}
groupedIssueIds={groupedIssueIds} groupedIssueIds={groupedIssueIds}
layout={displayFilters?.calendar?.layout} layout={displayFilters?.calendar?.layout}
@ -113,34 +99,21 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
<QuickActions <QuickActions
customActionButton={customActionButton} customActionButton={customActionButton}
issue={issue} issue={issue}
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.DELETE)} handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleUpdate={ handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
issueActions[EIssueActions.UPDATE] handleRemoveFromView={async () =>
? async (data) => handleIssues(issue.target_date ?? "", data, EIssueActions.UPDATE) removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)
: undefined
}
handleRemoveFromView={
issueActions[EIssueActions.REMOVE]
? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.REMOVE)
: undefined
}
handleArchive={
issueActions[EIssueActions.ARCHIVE]
? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.ARCHIVE)
: undefined
}
handleRestore={
issueActions[EIssueActions.RESTORE]
? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.RESTORE)
: undefined
} }
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
readOnly={!isEditingAllowed || isCompletedCycle} readOnly={!isEditingAllowed || isCompletedCycle}
/> />
)} )}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}
quickAddCallback={issueStore.quickAddIssue} quickAddCallback={issues.quickAddIssue}
viewId={viewId} viewId={viewId}
readOnly={!isEditingAllowed || isCompletedCycle} readOnly={!isEditingAllowed || isCompletedCycle}
updateFilters={updateFilters}
/> />
</DragDropContext> </DragDropContext>
</div> </div>

View File

@ -5,8 +5,10 @@ import { observer } from "mobx-react-lite";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues"; import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TGroupedIssues, TIssue, TIssueKanbanFilters, TIssueMap } from "@plane/types";
import { ICalendarWeek } from "./types";
// constants // constants
import { EIssuesStoreType } from "constants/issue"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
import { useCalendarView } from "hooks/store/use-calendar-view"; import { useCalendarView } from "hooks/store/use-calendar-view";
@ -14,8 +16,6 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssuesFilter } from "store/issue/module"; import { IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssuesFilter } from "store/issue/project"; import { IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssuesFilter } from "store/issue/project-views"; import { IProjectViewIssuesFilter } from "store/issue/project-views";
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
import { ICalendarWeek } from "./types";
type Props = { type Props = {
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter; issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
@ -33,6 +33,11 @@ type Props = {
addIssuesToView?: (issueIds: string[]) => Promise<any>; addIssuesToView?: (issueIds: string[]) => Promise<any>;
viewId?: string; viewId?: string;
readOnly?: boolean; readOnly?: boolean;
updateFilters?: (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
}; };
export const CalendarChart: React.FC<Props> = observer((props) => { export const CalendarChart: React.FC<Props> = observer((props) => {
@ -46,6 +51,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
quickAddCallback, quickAddCallback,
addIssuesToView, addIssuesToView,
viewId, viewId,
updateFilters,
readOnly = false, readOnly = false,
} = props; } = props;
// store hooks // store hooks
@ -74,7 +80,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
return ( return (
<> <>
<div className="flex h-full w-full flex-col overflow-hidden"> <div className="flex h-full w-full flex-col overflow-hidden">
<CalendarHeader issuesFilterStore={issuesFilterStore} viewId={viewId} /> <CalendarHeader issuesFilterStore={issuesFilterStore} updateFilters={updateFilters} />
<div className="flex h-full w-full vertical-scrollbar scrollbar-lg flex-col"> <div className="flex h-full w-full vertical-scrollbar scrollbar-lg flex-col">
<CalendarWeekHeader isLoading={!issues} showWeekends={showWeekends} /> <CalendarWeekHeader isLoading={!issues} showWeekends={showWeekends} />
<div className="h-full w-full"> <div className="h-full w-full">

View File

@ -9,6 +9,7 @@ import { Popover, Transition } from "@headlessui/react";
import { Check, ChevronUp } from "lucide-react"; import { Check, ChevronUp } from "lucide-react";
import { ToggleSwitch } from "@plane/ui"; import { ToggleSwitch } from "@plane/ui";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TCalendarLayouts, TIssueKanbanFilters } from "@plane/types";
// constants // constants
import { CALENDAR_LAYOUTS } from "constants/calendar"; import { CALENDAR_LAYOUTS } from "constants/calendar";
import { EIssueFilterType } from "constants/issue"; import { EIssueFilterType } from "constants/issue";
@ -17,18 +18,21 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssuesFilter } from "store/issue/module"; import { IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssuesFilter } from "store/issue/project"; import { IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssuesFilter } from "store/issue/project-views"; import { IProjectViewIssuesFilter } from "store/issue/project-views";
import { TCalendarLayouts } from "@plane/types";
interface ICalendarHeader { interface ICalendarHeader {
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter; issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
viewId?: string; updateFilters?: (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
} }
export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((props) => { export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((props) => {
const { issuesFilterStore, viewId } = props; const { issuesFilterStore, updateFilters } = props;
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { projectId } = router.query;
const issueCalendarView = useCalendarView(); const issueCalendarView = useCalendarView();
@ -51,20 +55,14 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
const showWeekends = issuesFilterStore.issueFilters?.displayFilters?.calendar?.show_weekends ?? false; const showWeekends = issuesFilterStore.issueFilters?.displayFilters?.calendar?.show_weekends ?? false;
const handleLayoutChange = (layout: TCalendarLayouts) => { const handleLayoutChange = (layout: TCalendarLayouts) => {
if (!workspaceSlug || !projectId) return; if (!projectId || !updateFilters) return;
issuesFilterStore.updateFilters( updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
workspaceSlug.toString(), calendar: {
projectId.toString(), ...issuesFilterStore.issueFilters?.displayFilters?.calendar,
EIssueFilterType.DISPLAY_FILTERS, layout,
{
calendar: {
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
layout,
},
}, },
viewId });
);
issueCalendarView.updateCalendarPayload( issueCalendarView.updateCalendarPayload(
layout === "month" layout === "month"
@ -76,20 +74,14 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
const handleToggleWeekends = () => { const handleToggleWeekends = () => {
const showWeekends = issuesFilterStore.issueFilters?.displayFilters?.calendar?.show_weekends ?? false; const showWeekends = issuesFilterStore.issueFilters?.displayFilters?.calendar?.show_weekends ?? false;
if (!workspaceSlug || !projectId) return; if (!projectId || !updateFilters) return;
issuesFilterStore.updateFilters( updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
workspaceSlug.toString(), calendar: {
projectId.toString(), ...issuesFilterStore.issueFilters?.displayFilters?.calendar,
EIssueFilterType.DISPLAY_FILTERS, show_weekends: !showWeekends,
{
calendar: {
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
show_weekends: !showWeekends,
},
}, },
viewId });
);
}; };
return ( return (

View File

@ -9,14 +9,25 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssuesFilter } from "store/issue/module"; import { IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssuesFilter } from "store/issue/project"; import { IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssuesFilter } from "store/issue/project-views"; import { IProjectViewIssuesFilter } from "store/issue/project-views";
import { EIssueFilterType } from "constants/issue";
import {
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
IIssueFilterOptions,
TIssueKanbanFilters,
} from "@plane/types";
interface ICalendarHeader { interface ICalendarHeader {
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter; issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
viewId?: string; updateFilters?: (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
} }
export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => { export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
const { issuesFilterStore, viewId } = props; const { issuesFilterStore, updateFilters } = props;
const issueCalendarView = useCalendarView(); const issueCalendarView = useCalendarView();
@ -101,7 +112,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
> >
Today Today
</button> </button>
<CalendarOptionsDropdown issuesFilterStore={issuesFilterStore} viewId={viewId} /> <CalendarOptionsDropdown issuesFilterStore={issuesFilterStore} updateFilters={updateFilters} />
</div> </div>
</div> </div>
); );

View File

@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react"; import { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
//hooks //hooks
@ -7,40 +7,15 @@ import { useCycle, useIssues } from "hooks/store";
import { CycleIssueQuickActions } from "components/issues"; import { CycleIssueQuickActions } from "components/issues";
import { BaseCalendarRoot } from "../base-calendar-root"; import { BaseCalendarRoot } from "../base-calendar-root";
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants // constants
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
export const CycleCalendarLayout: React.FC = observer(() => { export const CycleCalendarLayout: React.FC = observer(() => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCompletedCycleIds } = useCycle(); const { currentProjectCompletedCycleIds } = useCycle();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
const issueActions = useMemo( const { issues } = useIssues(EIssuesStoreType.CYCLE);
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId || !projectId) return;
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.archiveIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
},
}),
[issues, workspaceSlug, cycleId, projectId]
);
if (!cycleId) return null; if (!cycleId) return null;
@ -57,13 +32,11 @@ export const CycleCalendarLayout: React.FC = observer(() => {
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={issues}
issuesFilterStore={issuesFilter}
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}
issueActions={issueActions}
viewId={cycleId.toString()} viewId={cycleId.toString()}
isCompletedCycle={isCompletedCycle} isCompletedCycle={isCompletedCycle}
storeType={EIssuesStoreType.CYCLE}
/> />
); );
}); });

View File

@ -1,47 +1,22 @@
import { useCallback, useMemo } from "react"; import { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { useIssues } from "hooks/store";
// components // components
import { ModuleIssueQuickActions } from "components/issues"; import { ModuleIssueQuickActions } from "components/issues";
import { BaseCalendarRoot } from "../base-calendar-root"; import { BaseCalendarRoot } from "../base-calendar-root";
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants // constants
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
export const ModuleCalendarLayout: React.FC = observer(() => { export const ModuleCalendarLayout: React.FC = observer(() => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query as { const { workspaceSlug, projectId, moduleId } = router.query ;
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const issueActions = useMemo( const {issues} = useIssues(EIssuesStoreType.MODULE)
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => { if (!moduleId) return null;
if (!workspaceSlug || !moduleId) return;
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, moduleId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id, moduleId);
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssueFromModule(workspaceSlug, issue.project_id, moduleId, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.archiveIssue(workspaceSlug, issue.project_id, issue.id, moduleId);
},
}),
[issues, workspaceSlug, moduleId]
);
const addIssuesToView = useCallback( const addIssuesToView = useCallback(
(issueIds: string[]) => { (issueIds: string[]) => {
@ -53,12 +28,10 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={issues}
issuesFilterStore={issuesFilter}
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
issueActions={issueActions} storeType={EIssuesStoreType.MODULE}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}
viewId={moduleId} viewId={moduleId.toString()}
/> />
); );
}); });

View File

@ -1,48 +1,10 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks // hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
import { BaseCalendarRoot } from "../base-calendar-root"; import { BaseCalendarRoot } from "../base-calendar-root";
export const CalendarLayout: React.FC = observer(() => { export const CalendarLayout: React.FC = observer(() => (
const router = useRouter(); <BaseCalendarRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
const { workspaceSlug } = router.query; ));
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.archiveIssue(workspaceSlug.toString(), issue.project_id, issue.id);
},
}),
[issues, workspaceSlug]
);
return (
<BaseCalendarRoot
issueStore={issues}
issuesFilterStore={issuesFilter}
QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
/>
);
});

View File

@ -3,38 +3,21 @@ import { useRouter } from "next/router";
// hooks // hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
import { BaseCalendarRoot } from "../base-calendar-root"; import { BaseCalendarRoot } from "../base-calendar-root";
// constants // constants
export interface IViewCalendarLayout { export const ProjectViewCalendarLayout: React.FC = observer(() => {
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
};
}
export const ProjectViewCalendarLayout: React.FC<IViewCalendarLayout> = observer((props) => {
const { issueActions } = props;
// store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
// router // router
const router = useRouter(); const router = useRouter();
const { viewId } = router.query; const { viewId } = router.query;
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={issues}
issuesFilterStore={issuesFilter}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
viewId={viewId?.toString()} viewId={viewId?.toString()}
storeType={EIssuesStoreType.PROJECT_VIEW}
/> />
); );
}); });

View File

@ -1,21 +1,16 @@
import { DraggableLocation } from "@hello-pangea/dnd"; import { DraggableLocation } from "@hello-pangea/dnd";
import { ICycleIssues } from "store/issue/cycle"; import { TGroupedIssues, IIssueMap, TIssue } from "@plane/types";
import { IModuleIssues } from "store/issue/module";
import { IProjectIssues } from "store/issue/project";
import { IProjectViewIssues } from "store/issue/project-views";
import { TGroupedIssues, IIssueMap } from "@plane/types";
export const handleDragDrop = async ( export const handleDragDrop = async (
source: DraggableLocation, source: DraggableLocation,
destination: DraggableLocation, destination: DraggableLocation,
workspaceSlug: string | undefined, workspaceSlug: string | undefined,
projectId: string | undefined, projectId: string | undefined,
store: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues,
issueMap: IIssueMap, issueMap: IIssueMap,
issueWithIds: TGroupedIssues, issueWithIds: TGroupedIssues,
viewId: string | null = null // it can be moduleId, cycleId updateIssue?: (projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>
) => { ) => {
if (!issueMap || !issueWithIds || !workspaceSlug || !projectId) return; if (!issueMap || !issueWithIds || !workspaceSlug || !projectId || !updateIssue) return;
const sourceColumnId = source?.droppableId || null; const sourceColumnId = source?.droppableId || null;
const destinationColumnId = destination?.droppableId || null; const destinationColumnId = destination?.droppableId || null;
@ -31,12 +26,11 @@ export const handleDragDrop = async (
const [removed] = sourceIssues.splice(source.index, 1); const [removed] = sourceIssues.splice(source.index, 1);
const removedIssueDetail = issueMap[removed]; const removedIssueDetail = issueMap[removed];
const updateIssue = { const updatedIssue = {
id: removedIssueDetail?.id, id: removedIssueDetail?.id,
target_date: destinationColumnId, target_date: destinationColumnId,
}; };
if (viewId) return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId); return await updateIssue(projectId, updatedIssue.id, updatedIssue);
else return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
} }
}; };

View File

@ -11,11 +11,7 @@ import { IIssueFilterOptions } from "@plane/types";
export const CycleAppliedFiltersRoot: React.FC = observer(() => { export const CycleAppliedFiltersRoot: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query as { const { workspaceSlug, projectId, cycleId } = router.query;
workspaceSlug: string;
projectId: string;
cycleId: string;
};
// store hooks // store hooks
const { const {
issuesFilter: { issueFilters, updateFilters }, issuesFilter: { issueFilters, updateFilters },
@ -37,13 +33,13 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !cycleId) return; if (!workspaceSlug || !projectId || !cycleId) return;
if (!value) { if (!value) {
updateFilters( updateFilters(
workspaceSlug, workspaceSlug.toString(),
projectId, projectId.toString(),
EIssueFilterType.FILTERS, EIssueFilterType.FILTERS,
{ {
[key]: null, [key]: null,
}, },
cycleId cycleId.toString()
); );
return; return;
} }
@ -52,13 +48,13 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
newValues = newValues.filter((val) => val !== value); newValues = newValues.filter((val) => val !== value);
updateFilters( updateFilters(
workspaceSlug, workspaceSlug.toString(),
projectId, projectId.toString(),
EIssueFilterType.FILTERS, EIssueFilterType.FILTERS,
{ {
[key]: newValues, [key]: newValues,
}, },
cycleId cycleId.toString()
); );
}; };
@ -68,11 +64,17 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = null;
}); });
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, cycleId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ ...newFilters },
cycleId.toString()
);
}; };
// return if no filters are applied // return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null; if (Object.keys(appliedFilters).length === 0 || !workspaceSlug || !projectId) return null;
return ( return (
<div className="flex items-center justify-between p-4"> <div className="flex items-center justify-between p-4">
@ -84,7 +86,11 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
states={projectStates} states={projectStates}
/> />
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} /> <SaveFilterView
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
filterParams={appliedFilters}
/>
</div> </div>
); );
}); });

View File

@ -11,11 +11,7 @@ import { IIssueFilterOptions } from "@plane/types";
export const ModuleAppliedFiltersRoot: React.FC = observer(() => { export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query as { const { workspaceSlug, projectId, moduleId } = router.query;
workspaceSlug: string;
projectId: string;
moduleId: string;
};
// store hooks // store hooks
const { const {
issuesFilter: { issueFilters, updateFilters }, issuesFilter: { issueFilters, updateFilters },
@ -36,13 +32,13 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !moduleId) return; if (!workspaceSlug || !projectId || !moduleId) return;
if (!value) { if (!value) {
updateFilters( updateFilters(
workspaceSlug, workspaceSlug.toString(),
projectId, projectId.toString(),
EIssueFilterType.FILTERS, EIssueFilterType.FILTERS,
{ {
[key]: null, [key]: null,
}, },
moduleId moduleId.toString()
); );
return; return;
} }
@ -51,13 +47,13 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
newValues = newValues.filter((val) => val !== value); newValues = newValues.filter((val) => val !== value);
updateFilters( updateFilters(
workspaceSlug, workspaceSlug.toString(),
projectId, projectId.toString(),
EIssueFilterType.FILTERS, EIssueFilterType.FILTERS,
{ {
[key]: newValues, [key]: newValues,
}, },
moduleId moduleId.toString()
); );
}; };
@ -67,11 +63,17 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = null;
}); });
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, moduleId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ ...newFilters },
moduleId.toString()
);
}; };
// return if no filters are applied // return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null; if (!workspaceSlug || !projectId || Object.keys(appliedFilters).length === 0) return null;
return ( return (
<div className="flex items-center justify-between p-4"> <div className="flex items-center justify-between p-4">
@ -83,7 +85,11 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
states={projectStates} states={projectStates}
/> />
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} /> <SaveFilterView
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
filterParams={appliedFilters}
/>
</div> </div>
); );
}); });

View File

@ -14,11 +14,7 @@ import { IIssueFilterOptions } from "@plane/types";
export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query as { const { workspaceSlug, projectId, viewId } = router.query;
workspaceSlug: string;
projectId: string;
viewId: string;
};
// store hooks // store hooks
const { const {
issuesFilter: { issueFilters, updateFilters }, issuesFilter: { issueFilters, updateFilters },
@ -39,16 +35,16 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
}); });
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !viewId) return;
if (!value) { if (!value) {
updateFilters( updateFilters(
workspaceSlug, workspaceSlug.toString(),
projectId, projectId.toString(),
EIssueFilterType.FILTERS, EIssueFilterType.FILTERS,
{ {
[key]: null, [key]: null,
}, },
viewId viewId.toString()
); );
return; return;
} }
@ -57,23 +53,29 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
newValues = newValues.filter((val) => val !== value); newValues = newValues.filter((val) => val !== value);
updateFilters( updateFilters(
workspaceSlug, workspaceSlug.toString(),
projectId, projectId.toString(),
EIssueFilterType.FILTERS, EIssueFilterType.FILTERS,
{ {
[key]: newValues, [key]: newValues,
}, },
viewId viewId.toString()
); );
}; };
const handleClearAllFilters = () => { const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !viewId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = null;
}); });
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, viewId); updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ ...newFilters },
viewId.toString()
);
}; };
const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters); const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters);

View File

@ -7,44 +7,43 @@ import { GanttQuickAddIssueForm, IssueGanttBlock } from "components/issues";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { renderIssueBlocksStructure } from "helpers/issue.helper"; import { renderIssueBlocksStructure } from "helpers/issue.helper";
import { useIssues, useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// components // components
// helpers // helpers
// types // types
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
import { TIssue, TUnGroupedIssues } from "@plane/types"; import { TIssue, TUnGroupedIssues } from "@plane/types";
// constants // constants
import { EIssueActions } from "../types"; import { EIssuesStoreType } from "constants/issue";
type GanttStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW;
interface IBaseGanttRoot { interface IBaseGanttRoot {
issueFiltersStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
issueStore: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues;
viewId?: string; viewId?: string;
issueActions: { storeType: GanttStoreType;
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
};
} }
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => { export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
const { issueFiltersStore, issueStore, viewId } = props; const { viewId, storeType } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { issues, issuesFilter } = useIssues(storeType);
const { updateIssue } = useIssuesActions(storeType);
// store hooks // store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { issueMap } = useIssues(); const { issueMap } = useIssues();
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters; const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters;
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues; const issueIds = (issues.groupedIssueIds ?? []) as TUnGroupedIssues;
const { enableIssueCreation } = issueStore?.viewFlags || {}; const { enableIssueCreation } = issues?.viewFlags || {};
const issues = issueIds.map((id) => issueMap?.[id]); const issuesArray = issueIds.map((id) => issueMap?.[id]);
const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => { const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -52,7 +51,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
const payload: any = { ...data }; const payload: any = { ...data };
if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder; if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
await issueStore.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, payload, viewId); updateIssue && (await updateIssue(issue.project_id, issue.id, payload));
}; };
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@ -64,7 +63,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
border={false} border={false}
title="Issues" title="Issues"
loaderTitle="Issues" loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issues as TIssue[]) : null} blocks={issues ? renderIssueBlocksStructure(issuesArray) : null}
blockUpdateHandler={updateIssueBlockStructure} blockUpdateHandler={updateIssueBlockStructure}
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />} blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />} sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
@ -75,7 +74,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
enableAddBlock={isAllowed} enableAddBlock={isAllowed}
quickAdd={ quickAdd={
enableIssueCreation && isAllowed ? ( enableIssueCreation && isAllowed ? (
<GanttQuickAddIssueForm quickAddCallback={issueStore.quickAddIssue} viewId={viewId} /> <GanttQuickAddIssueForm quickAddCallback={issues.quickAddIssue} viewId={viewId} />
) : undefined ) : undefined
} }
showAllBlocks showAllBlocks

View File

@ -2,47 +2,13 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
export const CycleGanttLayout: React.FC = observer(() => { export const CycleGanttLayout: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, cycleId } = router.query; const { cycleId } = router.query;
// store hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { fetchCycleDetails } = useCycle();
const issueActions = { return <BaseGanttRoot viewId={cycleId?.toString()} storeType={EIssuesStoreType.CYCLE} />;
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId || !issue.id) return;
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId.toString());
},
};
return (
<BaseGanttRoot
issueActions={issueActions}
issueFiltersStore={issuesFilter}
issueStore={issues}
viewId={cycleId?.toString()}
/>
);
}); });

View File

@ -2,47 +2,13 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues, useModule } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
export const ModuleGanttLayout: React.FC = observer(() => { export const ModuleGanttLayout: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, moduleId } = router.query; const { moduleId } = router.query;
// store hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const { fetchModuleDetails } = useModule();
const issueActions = { return <BaseGanttRoot viewId={moduleId?.toString()} storeType={EIssuesStoreType.MODULE} />;
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, moduleId.toString());
fetchModuleDetails(workspaceSlug.toString(), issue.project_id, moduleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
fetchModuleDetails(workspaceSlug.toString(), issue.project_id, moduleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId || !issue.id) return;
await issues.removeIssueFromModule(workspaceSlug.toString(), issue.project_id, moduleId.toString(), issue.id);
fetchModuleDetails(workspaceSlug.toString(), issue.project_id, moduleId.toString());
},
};
return (
<BaseGanttRoot
issueActions={issueActions}
issueFiltersStore={issuesFilter}
issueStore={issues}
viewId={moduleId?.toString()}
/>
);
}); });

View File

@ -1,33 +1,8 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks // hooks
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
export const GanttLayout: React.FC = observer(() => { export const GanttLayout: React.FC = observer(() =>( <BaseGanttRoot storeType={EIssuesStoreType.PROJECT} />));
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
const issueActions = {
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
},
};
return <BaseGanttRoot issueFiltersStore={issuesFilter} issueStore={issues} issueActions={issueActions} />;
});

View File

@ -2,36 +2,15 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
// constants // constants
// types // types
export interface IViewGanttLayout { export const ProjectViewGanttLayout: React.FC = observer(() => {
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
};
}
export const ProjectViewGanttLayout: React.FC<IViewGanttLayout> = observer((props) => {
const { issueActions } = props;
// store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
// router // router
const router = useRouter(); const router = useRouter();
const { viewId } = router.query; const { viewId } = router.query;
return ( return <BaseGanttRoot viewId={viewId?.toString()} storeType={EIssuesStoreType.PROJECT_VIEW} />;
<BaseGanttRoot
issueFiltersStore={issuesFilter}
issueStore={issues}
issueActions={issueActions}
viewId={viewId?.toString()}
/>
);
}); });

View File

@ -6,45 +6,31 @@ import { useRouter } from "next/router";
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui"; import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
import { DeleteIssueModal } from "components/issues"; import { DeleteIssueModal } from "components/issues";
import { ISSUE_DELETED } from "constants/event-tracker"; import { ISSUE_DELETED } from "constants/event-tracker";
import { EIssueFilterType, TCreateModalStoreTypes } from "constants/issue"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useEventTracker, useIssues, useUser } from "hooks/store"; import { useEventTracker, useIssues, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// ui // ui
// types // types
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
import { IDraftIssues, IDraftIssuesFilter } from "store/issue/draft";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EIssueActions } from "../types";
//components //components
import { KanBan } from "./default"; import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes"; import { KanBanSwimLanes } from "./swimlanes";
import { handleDragDrop } from "./utils"; import { handleDragDrop } from "./utils";
export type KanbanStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.DRAFT
| EIssuesStoreType.PROFILE;
export interface IBaseKanBanLayout { export interface IBaseKanBanLayout {
issues: IProjectIssues | ICycleIssues | IDraftIssues | IModuleIssues | IProjectViewIssues | IProfileIssues;
issuesFilter:
| IProjectIssuesFilter
| IModuleIssuesFilter
| ICycleIssuesFilter
| IDraftIssuesFilter
| IProjectViewIssuesFilter
| IProfileIssuesFilter;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.RESTORE]?: (issue: TIssue) => Promise<void>;
};
showLoader?: boolean; showLoader?: boolean;
viewId?: string; viewId?: string;
storeType?: TCreateModalStoreTypes; storeType: KanbanStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<any>; addIssuesToView?: (issueIds: string[]) => Promise<any>;
canEditPropertiesBasedOnProject?: (projectId: string) => boolean; canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
isCompletedCycle?: boolean; isCompletedCycle?: boolean;
@ -58,10 +44,7 @@ type KanbanDragState = {
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => { export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
const { const {
issues,
issuesFilter,
QuickActions, QuickActions,
issueActions,
showLoader, showLoader,
viewId, viewId,
storeType, storeType,
@ -77,7 +60,9 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { captureIssueEvent } = useEventTracker(); const { captureIssueEvent } = useEventTracker();
const { issueMap } = useIssues(); const { issueMap, issuesFilter, issues } = useIssues(storeType);
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
useIssuesActions(storeType);
const issueIds = issues?.groupedIssueIds || []; const issueIds = issues?.groupedIssueIds || [];
@ -148,12 +133,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
result.destination, result.destination,
workspaceSlug?.toString(), workspaceSlug?.toString(),
projectId?.toString(), projectId?.toString(),
issues,
sub_group_by, sub_group_by,
group_by, group_by,
issueMap, issueMap,
issueIds, issueIds,
viewId updateIssue,
removeIssue
).catch((err) => { ).catch((err) => {
setToast({ setToast({
title: "Error", title: "Error",
@ -165,55 +150,39 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
} }
}; };
const handleIssues = useCallback(
async (issue: TIssue, action: EIssueActions) => {
if (issueActions[action]) {
await issueActions[action]!(issue);
}
},
[issueActions]
);
const renderQuickActions = useCallback( const renderQuickActions = useCallback(
(issue: TIssue, customActionButton?: React.ReactElement) => ( (issue: TIssue, customActionButton?: React.ReactElement) => (
<QuickActions <QuickActions
customActionButton={customActionButton} customActionButton={customActionButton}
issue={issue} issue={issue}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleUpdate={ handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
} handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRemoveFromView={ handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined
}
handleArchive={
issueActions[EIssueActions.ARCHIVE] ? async () => handleIssues(issue, EIssueActions.ARCHIVE) : undefined
}
handleRestore={
issueActions[EIssueActions.RESTORE] ? async () => handleIssues(issue, EIssueActions.RESTORE) : undefined
}
readOnly={!isEditingAllowed || isCompletedCycle} readOnly={!isEditingAllowed || isCompletedCycle}
/> />
), ),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[issueActions, handleIssues] [isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
); );
const handleDeleteIssue = async () => { const handleDeleteIssue = async () => {
if (!handleDragDrop) return; if (!handleDragDrop || !dragState.draggedIssueId) return;
await handleDragDrop( await handleDragDrop(
dragState.source, dragState.source,
dragState.destination, dragState.destination,
workspaceSlug?.toString(), workspaceSlug?.toString(),
projectId?.toString(), projectId?.toString(),
issues,
sub_group_by, sub_group_by,
group_by, group_by,
issueMap, issueMap,
issueIds, issueIds,
viewId updateIssue,
removeIssue
).finally(() => { ).finally(() => {
handleIssues(issueMap[dragState.draggedIssueId!], EIssueActions.DELETE); const draggedIssue = issueMap[dragState.draggedIssueId!];
removeIssue(draggedIssue.project_id, draggedIssue.id);
setDeleteIssueModal(false); setDeleteIssueModal(false);
setDragState({}); setDragState({});
captureIssueEvent({ captureIssueEvent({
@ -229,14 +198,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || []; let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
if (kanbanFilters.includes(value)) kanbanFilters = kanbanFilters.filter((_value) => _value != value); if (kanbanFilters.includes(value)) kanbanFilters = kanbanFilters.filter((_value) => _value != value);
else kanbanFilters.push(value); else kanbanFilters.push(value);
issuesFilter.updateFilters( updateFilters(
workspaceSlug.toString(),
projectId.toString(), projectId.toString(),
EIssueFilterType.KANBAN_FILTERS, EIssueFilterType.KANBAN_FILTERS,
{ {
[toggle]: kanbanFilters, [toggle]: kanbanFilters,
}, }
viewId
); );
} }
}; };
@ -294,7 +261,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
displayProperties={displayProperties} displayProperties={displayProperties}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={renderQuickActions} quickActions={renderQuickActions}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}

View File

@ -12,7 +12,6 @@ import { IssueProperties } from "../properties/all-properties";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
// ui // ui
// types // types
import { EIssueActions } from "../types";
// helper // helper
interface IssueBlockProps { interface IssueBlockProps {
@ -23,7 +22,7 @@ interface IssueBlockProps {
isDragDisabled: boolean; isDragDisabled: boolean;
draggableId: string; draggableId: string;
index: number; index: number;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue) => React.ReactNode; quickActions: (issue: TIssue) => React.ReactNode;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>; scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
@ -34,13 +33,13 @@ interface IssueBlockProps {
interface IssueDetailsBlockProps { interface IssueDetailsBlockProps {
issue: TIssue; issue: TIssue;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue) => React.ReactNode; quickActions: (issue: TIssue) => React.ReactNode;
isReadOnly: boolean; isReadOnly: boolean;
} }
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((props: IssueDetailsBlockProps) => { const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((props: IssueDetailsBlockProps) => {
const { issue, handleIssues, quickActions, isReadOnly, displayProperties } = props; const { issue, updateIssue, quickActions, isReadOnly, displayProperties } = props;
// hooks // hooks
const { getProjectIdentifierById } = useProject(); const { getProjectIdentifierById } = useProject();
const { const {
@ -48,10 +47,6 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
} = useApplication(); } = useApplication();
const { setPeekIssue } = useIssueDetail(); const { setPeekIssue } = useIssueDetail();
const updateIssue = async (issueToUpdate: TIssue) => {
if (issueToUpdate) await handleIssues(issueToUpdate, EIssueActions.UPDATE);
};
const handleIssuePeekOverview = (issue: TIssue) => const handleIssuePeekOverview = (issue: TIssue) =>
workspaceSlug && workspaceSlug &&
issue && issue &&
@ -95,7 +90,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
issue={issue} issue={issue}
displayProperties={displayProperties} displayProperties={displayProperties}
activeLayout="Kanban" activeLayout="Kanban"
handleIssues={updateIssue} updateIssue={updateIssue}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
/> />
</> </>
@ -111,7 +106,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
isDragDisabled, isDragDisabled,
draggableId, draggableId,
index, index,
handleIssues, updateIssue,
quickActions, quickActions,
canEditProperties, canEditProperties,
scrollableContainerRef, scrollableContainerRef,
@ -159,7 +154,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
<KanbanIssueDetailsBlock <KanbanIssueDetailsBlock
issue={issue} issue={issue}
displayProperties={displayProperties} displayProperties={displayProperties}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
isReadOnly={!canEditIssueProperties} isReadOnly={!canEditIssueProperties}
/> />

View File

@ -2,7 +2,6 @@ import { MutableRefObject, memo } from "react";
//types //types
import { KanbanIssueBlock } from "components/issues"; import { KanbanIssueBlock } from "components/issues";
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types"; import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
import { EIssueActions } from "../types";
// components // components
interface IssueBlocksListProps { interface IssueBlocksListProps {
@ -13,7 +12,7 @@ interface IssueBlocksListProps {
issueIds: string[]; issueIds: string[];
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
isDragDisabled: boolean; isDragDisabled: boolean;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>; scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
@ -29,7 +28,7 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
issueIds, issueIds,
displayProperties, displayProperties,
isDragDisabled, isDragDisabled,
handleIssues, updateIssue,
quickActions, quickActions,
canEditProperties, canEditProperties,
scrollableContainerRef, scrollableContainerRef,
@ -54,7 +53,7 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
issueId={issueId} issueId={issueId}
issuesMap={issuesMap} issuesMap={issuesMap}
displayProperties={displayProperties} displayProperties={displayProperties}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
draggableId={draggableId} draggableId={draggableId}
index={index} index={index}

View File

@ -1,7 +1,6 @@
import { MutableRefObject } from "react"; import { MutableRefObject } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// constants // constants
import { TCreateModalStoreTypes } from "constants/issue";
// hooks // hooks
import { import {
useCycle, useCycle,
@ -26,11 +25,11 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
} from "@plane/types"; } from "@plane/types";
// parent components // parent components
import { EIssueActions } from "../types";
import { getGroupByColumns } from "../utils"; import { getGroupByColumns } from "../utils";
// components // components
import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card";
import { KanbanGroup } from "./kanban-group"; import { KanbanGroup } from "./kanban-group";
import { KanbanStoreType } from "./base-kanban-root";
export interface IGroupByKanBan { export interface IGroupByKanBan {
issuesMap: IIssueMap; issuesMap: IIssueMap;
@ -40,7 +39,7 @@ export interface IGroupByKanBan {
group_by: string | null; group_by: string | null;
sub_group_id: string; sub_group_id: string;
isDragDisabled: boolean; isDragDisabled: boolean;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
kanbanFilters: TIssueKanbanFilters; kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: any; handleKanbanFilters: any;
@ -53,7 +52,7 @@ export interface IGroupByKanBan {
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType?: TCreateModalStoreTypes; storeType: KanbanStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>; scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
@ -70,7 +69,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
group_by, group_by,
sub_group_id = "null", sub_group_id = "null",
isDragDisabled, isDragDisabled,
handleIssues, updateIssue,
quickActions, quickActions,
kanbanFilters, kanbanFilters,
handleKanbanFilters, handleKanbanFilters,
@ -164,7 +163,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
group_by={group_by} group_by={group_by}
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
isDragDisabled={isDragDisabled} isDragDisabled={isDragDisabled}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
enableQuickIssueCreate={enableQuickIssueCreate} enableQuickIssueCreate={enableQuickIssueCreate}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
@ -190,7 +189,7 @@ export interface IKanBan {
sub_group_by: string | null; sub_group_by: string | null;
group_by: string | null; group_by: string | null;
sub_group_id?: string; sub_group_id?: string;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
kanbanFilters: TIssueKanbanFilters; kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void; handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
@ -204,7 +203,7 @@ export interface IKanBan {
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType?: TCreateModalStoreTypes; storeType: KanbanStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>; scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
@ -219,7 +218,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
sub_group_by, sub_group_by,
group_by, group_by,
sub_group_id = "null", sub_group_id = "null",
handleIssues, updateIssue,
quickActions, quickActions,
kanbanFilters, kanbanFilters,
handleKanbanFilters, handleKanbanFilters,
@ -246,7 +245,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
isDragDisabled={!issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by)} isDragDisabled={!issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by)}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}

View File

@ -9,11 +9,11 @@ import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
import { ExistingIssuesListModal } from "components/core"; import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal } from "components/issues"; import { CreateUpdateIssueModal } from "components/issues";
// constants // constants
import { TCreateModalStoreTypes } from "constants/issue";
// hooks // hooks
import { useEventTracker } from "hooks/store"; import { useEventTracker } from "hooks/store";
// types // types
import { TIssue, ISearchIssueResponse, TIssueKanbanFilters } from "@plane/types"; import { TIssue, ISearchIssueResponse, TIssueKanbanFilters } from "@plane/types";
import { KanbanStoreType } from "../base-kanban-root";
interface IHeaderGroupByCard { interface IHeaderGroupByCard {
sub_group_by: string | null; sub_group_by: string | null;
@ -26,7 +26,7 @@ interface IHeaderGroupByCard {
handleKanbanFilters: any; handleKanbanFilters: any;
issuePayload: Partial<TIssue>; issuePayload: Partial<TIssue>;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType?: TCreateModalStoreTypes; storeType: KanbanStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
} }

View File

@ -12,7 +12,6 @@ import {
TSubGroupedIssues, TSubGroupedIssues,
TUnGroupedIssues, TUnGroupedIssues,
} from "@plane/types"; } from "@plane/types";
import { EIssueActions } from "../types";
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "."; import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
interface IKanbanGroup { interface IKanbanGroup {
@ -25,7 +24,7 @@ interface IKanbanGroup {
group_by: string | null; group_by: string | null;
sub_group_id: string; sub_group_id: string;
isDragDisabled: boolean; isDragDisabled: boolean;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
enableQuickIssueCreate?: boolean; enableQuickIssueCreate?: boolean;
quickAddCallback?: ( quickAddCallback?: (
@ -53,7 +52,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
issueIds, issueIds,
peekIssueId, peekIssueId,
isDragDisabled, isDragDisabled,
handleIssues, updateIssue,
quickActions, quickActions,
canEditProperties, canEditProperties,
enableQuickIssueCreate, enableQuickIssueCreate,
@ -135,7 +134,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []} issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
displayProperties={displayProperties} displayProperties={displayProperties}
isDragDisabled={isDragDisabled} isDragDisabled={isDragDisabled}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef} scrollableContainerRef={scrollableContainerRef}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from "react"; import React, { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
@ -7,8 +7,6 @@ import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store"; import { useCycle, useIssues } from "hooks/store";
// ui // ui
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// components // components
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
@ -19,35 +17,9 @@ export const CycleKanBanLayout: React.FC = observer(() => {
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
// store // store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE); const { issues } = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCompletedCycleIds } = useCycle(); const { currentProjectCompletedCycleIds } = useCycle();
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.archiveIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
},
}),
[issues, workspaceSlug, cycleId]
);
const isCompletedCycle = const isCompletedCycle =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false; cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
@ -63,9 +35,6 @@ export const CycleKanBanLayout: React.FC = observer(() => {
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions}
issues={issues}
issuesFilter={issuesFilter}
showLoader showLoader
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
viewId={cycleId?.toString() ?? ""} viewId={cycleId?.toString() ?? ""}

View File

@ -1,49 +1,11 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; // components
// hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
// constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
export interface IKanBanLayout {} export interface IKanBanLayout {}
export const DraftKanBanLayout: React.FC = observer(() => { export const DraftKanBanLayout: React.FC = observer(() => (
const router = useRouter(); <BaseKanBanRoot showLoader QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.DRAFT} />
const { workspaceSlug } = router.query; ));
// store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
},
}),
[issues, workspaceSlug]
);
return (
<BaseKanBanRoot
issueActions={issueActions}
issuesFilter={issuesFilter}
issues={issues}
showLoader
QuickActions={ProjectIssueQuickActions}
/>
);
});

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hook // hook
@ -7,9 +7,7 @@ import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store"; import { useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
// constants // constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
export interface IModuleKanBanLayout {} export interface IModuleKanBanLayout {}
@ -19,39 +17,10 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
// store // store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE); const { issues } = useIssues(EIssuesStoreType.MODULE);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, moduleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssueFromModule(workspaceSlug.toString(), issue.project_id, moduleId.toString(), issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.archiveIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
},
}),
[issues, workspaceSlug, moduleId]
);
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions}
issues={issues}
issuesFilter={issuesFilter}
showLoader showLoader
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
viewId={moduleId?.toString()} viewId={moduleId?.toString()}

View File

@ -1,48 +1,18 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks // hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store"; import { useUser } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
// constants // constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
export const ProfileIssuesKanBanLayout: React.FC = observer(() => { export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string };
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROFILE);
const { const {
membership: { currentWorkspaceAllProjectsRole }, membership: { currentWorkspaceAllProjectsRole },
} = useUser(); } = useUser();
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, userId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id, userId);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.archiveIssue(workspaceSlug, issue.project_id, issue.id, userId);
},
}),
[issues, workspaceSlug, userId]
);
const canEditPropertiesBasedOnProject = (projectId: string) => { const canEditPropertiesBasedOnProject = (projectId: string) => {
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId]; const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
@ -52,9 +22,6 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions}
issuesFilter={issuesFilter}
issues={issues}
showLoader showLoader
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
storeType={EIssuesStoreType.PROFILE} storeType={EIssuesStoreType.PROFILE}

View File

@ -1,54 +1,12 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx store // mobx store
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store/use-issues";
// components // components
// types // types
import { TIssue } from "@plane/types";
// constants // constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
export interface IKanBanLayout {} export const KanBanLayout: React.FC = observer(() => (
<BaseKanBanRoot showLoader QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
export const KanBanLayout: React.FC = observer(() => { ));
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string };
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.archiveIssue(workspaceSlug, issue.project_id, issue.id);
},
}),
[issues, workspaceSlug]
);
return (
<BaseKanBanRoot
issueActions={issueActions}
issues={issues}
issuesFilter={issuesFilter}
showLoader
QuickActions={ProjectIssueQuickActions}
storeType={EIssuesStoreType.PROJECT}
/>
);
});

View File

@ -3,37 +3,19 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// constant // constant
// types // types
import { TIssue } from "@plane/types";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
// components // components
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
export interface IViewKanBanLayout { export const ProjectViewKanBanLayout: React.FC = observer(() => {
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
};
}
export const ProjectViewKanBanLayout: React.FC<IViewKanBanLayout> = observer((props) => {
const { issueActions } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { viewId } = router.query; const { viewId } = router.query;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions}
issuesFilter={issuesFilter}
issues={issues}
showLoader showLoader
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
storeType={EIssuesStoreType.PROJECT_VIEW} storeType={EIssuesStoreType.PROJECT_VIEW}

View File

@ -1,7 +1,6 @@
import { MutableRefObject } from "react"; import { MutableRefObject } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
import { TCreateModalStoreTypes } from "constants/issue";
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store"; import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store";
import { import {
GroupByColumnTypes, GroupByColumnTypes,
@ -14,11 +13,11 @@ import {
TUnGroupedIssues, TUnGroupedIssues,
TIssueKanbanFilters, TIssueKanbanFilters,
} from "@plane/types"; } from "@plane/types";
import { EIssueActions } from "../types";
import { getGroupByColumns } from "../utils"; import { getGroupByColumns } from "../utils";
import { KanBan } from "./default"; import { KanBan } from "./default";
import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card";
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card"; import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
import { KanbanStoreType } from "./base-kanban-root";
// types // types
// constants // constants
@ -29,6 +28,7 @@ interface ISubGroupSwimlaneHeader {
list: IGroupByColumn[]; list: IGroupByColumn[];
kanbanFilters: TIssueKanbanFilters; kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void; handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
storeType: KanbanStoreType;
} }
const getSubGroupHeaderIssuesCount = (issueIds: TSubGroupedIssues, groupById: string) => { const getSubGroupHeaderIssuesCount = (issueIds: TSubGroupedIssues, groupById: string) => {
@ -43,6 +43,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
issueIds, issueIds,
sub_group_by, sub_group_by,
group_by, group_by,
storeType,
list, list,
kanbanFilters, kanbanFilters,
handleKanbanFilters, handleKanbanFilters,
@ -62,6 +63,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
issuePayload={_list.payload} issuePayload={_list.payload}
storeType={storeType}
/> />
</div> </div>
))} ))}
@ -73,13 +75,13 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues; issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
showEmptyGroup: boolean; showEmptyGroup: boolean;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
kanbanFilters: TIssueKanbanFilters; kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void; handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
isDragStarted?: boolean; isDragStarted?: boolean;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType?: TCreateModalStoreTypes; storeType: KanbanStoreType;
enableQuickIssueCreate: boolean; enableQuickIssueCreate: boolean;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
@ -99,7 +101,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
sub_group_by, sub_group_by,
group_by, group_by,
list, list,
handleIssues, storeType,
updateIssue,
quickActions, quickActions,
displayProperties, displayProperties,
kanbanFilters, kanbanFilters,
@ -153,7 +156,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
sub_group_id={_list.id} sub_group_id={_list.id}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
@ -165,6 +168,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
viewId={viewId} viewId={viewId}
scrollableContainerRef={scrollableContainerRef} scrollableContainerRef={scrollableContainerRef}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
storeType={storeType}
/> />
</div> </div>
)} )}
@ -180,14 +184,14 @@ export interface IKanBanSwimLanes {
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
sub_group_by: string | null; sub_group_by: string | null;
group_by: string | null; group_by: string | null;
handleIssues: (issue: TIssue, action: EIssueActions) => void; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
kanbanFilters: TIssueKanbanFilters; kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void; handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
showEmptyGroup: boolean; showEmptyGroup: boolean;
isDragStarted?: boolean; isDragStarted?: boolean;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType?: TCreateModalStoreTypes; storeType: KanbanStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
enableQuickIssueCreate: boolean; enableQuickIssueCreate: boolean;
quickAddCallback?: ( quickAddCallback?: (
@ -208,7 +212,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
displayProperties, displayProperties,
sub_group_by, sub_group_by,
group_by, group_by,
handleIssues, updateIssue,
storeType,
quickActions, quickActions,
kanbanFilters, kanbanFilters,
handleKanbanFilters, handleKanbanFilters,
@ -261,6 +266,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
list={groupByList} list={groupByList}
storeType={storeType}
/> />
</div> </div>
@ -272,7 +278,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
displayProperties={displayProperties} displayProperties={displayProperties}
group_by={group_by} group_by={group_by}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
@ -285,6 +291,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
scrollableContainerRef={scrollableContainerRef} scrollableContainerRef={scrollableContainerRef}
storeType={storeType}
/> />
)} )}
</div> </div>

View File

@ -1,12 +1,5 @@
import { DraggableLocation } from "@hello-pangea/dnd"; import { DraggableLocation } from "@hello-pangea/dnd";
import { ICycleIssues } from "store/issue/cycle"; import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TUnGroupedIssues, TIssue } from "@plane/types";
import { IDraftIssues } from "store/issue/draft";
import { IModuleIssues } from "store/issue/module";
import { IProfileIssues } from "store/issue/profile";
import { IProjectIssues } from "store/issue/project";
import { IProjectViewIssues } from "store/issue/project-views";
import { IWorkspaceIssues } from "store/issue/workspace";
import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TUnGroupedIssues } from "@plane/types";
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => { const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
const sortOrderDefaultValue = 65535; const sortOrderDefaultValue = 65535;
@ -48,24 +41,16 @@ export const handleDragDrop = async (
destination: DraggableLocation | null | undefined, destination: DraggableLocation | null | undefined,
workspaceSlug: string | undefined, workspaceSlug: string | undefined,
projectId: string | undefined, // projectId for all views or user id in profile issues projectId: string | undefined, // projectId for all views or user id in profile issues
store:
| IProjectIssues
| ICycleIssues
| IDraftIssues
| IModuleIssues
| IDraftIssues
| IProjectViewIssues
| IProfileIssues
| IWorkspaceIssues,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issueMap: IIssueMap, issueMap: IIssueMap,
issueWithIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined, issueWithIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined,
viewId: string | null = null // it can be moduleId, cycleId updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined,
removeIssue: (projectId: string, issueId: string) => Promise<void> | undefined
) => { ) => {
if (!issueMap || !issueWithIds || !source || !destination || !workspaceSlug || !projectId) return; if (!issueMap || !issueWithIds || !source || !destination || !workspaceSlug || !projectId) return;
let updateIssue: any = {}; let updatedIssue: any = {};
const sourceDroppableId = source?.droppableId; const sourceDroppableId = source?.droppableId;
const destinationDroppableId = destination?.droppableId; const destinationDroppableId = destination?.droppableId;
@ -100,8 +85,7 @@ export const handleDragDrop = async (
const [removed] = sourceIssues.splice(source.index, 1); const [removed] = sourceIssues.splice(source.index, 1);
if (removed) { if (removed) {
if (viewId) return await store?.removeIssue(workspaceSlug, projectId, removed); //, viewId); return await removeIssue(projectId, removed);
else return await store?.removeIssue(workspaceSlug, projectId, removed);
} }
} else { } else {
//spreading the array to stop changing the original reference //spreading the array to stop changing the original reference
@ -118,14 +102,14 @@ export const handleDragDrop = async (
const [removed] = sourceIssues.splice(source.index, 1); const [removed] = sourceIssues.splice(source.index, 1);
const removedIssueDetail = issueMap[removed]; const removedIssueDetail = issueMap[removed];
updateIssue = { updatedIssue = {
id: removedIssueDetail?.id, id: removedIssueDetail?.id,
project_id: removedIssueDetail?.project_id, project_id: removedIssueDetail?.project_id,
}; };
// for both horizontal and vertical dnd // for both horizontal and vertical dnd
updateIssue = { updatedIssue = {
...updateIssue, ...updatedIssue,
...handleSortOrder( ...handleSortOrder(
sourceDroppableId === destinationDroppableId ? sourceIssues : destinationIssues, sourceDroppableId === destinationDroppableId ? sourceIssues : destinationIssues,
destination.index, destination.index,
@ -136,19 +120,19 @@ export const handleDragDrop = async (
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) { if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) { if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
if (sourceGroupByColumnId != destinationGroupByColumnId) { if (sourceGroupByColumnId != destinationGroupByColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state_id: destinationGroupByColumnId }; if (groupBy === "state") updatedIssue = { ...updatedIssue, state_id: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId }; if (groupBy === "priority") updatedIssue = { ...updatedIssue, priority: destinationGroupByColumnId };
} }
} else { } else {
if (subGroupBy === "state") if (subGroupBy === "state")
updateIssue = { updatedIssue = {
...updateIssue, ...updatedIssue,
state_id: destinationSubGroupByColumnId, state_id: destinationSubGroupByColumnId,
priority: destinationGroupByColumnId, priority: destinationGroupByColumnId,
}; };
if (subGroupBy === "priority") if (subGroupBy === "priority")
updateIssue = { updatedIssue = {
...updateIssue, ...updatedIssue,
state_id: destinationGroupByColumnId, state_id: destinationGroupByColumnId,
priority: destinationSubGroupByColumnId, priority: destinationSubGroupByColumnId,
}; };
@ -156,15 +140,13 @@ export const handleDragDrop = async (
} else { } else {
// for horizontal dnd // for horizontal dnd
if (sourceColumnId != destinationColumnId) { if (sourceColumnId != destinationColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state_id: destinationGroupByColumnId }; if (groupBy === "state") updatedIssue = { ...updatedIssue, state_id: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId }; if (groupBy === "priority") updatedIssue = { ...updatedIssue, priority: destinationGroupByColumnId };
} }
} }
if (updateIssue && updateIssue?.id) { if (updatedIssue && updatedIssue?.id) {
if (viewId) return updateIssue && (await updateIssue(updatedIssue.project_id, updatedIssue.id, updatedIssue));
return await store?.updateIssue(workspaceSlug, updateIssue.project_id, updateIssue.id, updateIssue, viewId);
else return await store?.updateIssue(workspaceSlug, updateIssue.project_id, updateIssue.id, updateIssue);
} }
} }
}; };

View File

@ -1,68 +1,46 @@
import { FC, useCallback } from "react"; import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// types // types
import { TCreateModalStoreTypes } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
import { IArchivedIssuesFilter, IArchivedIssues } from "store/issue/archived";
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle"; import { TIssue } from "@plane/types"
import { IDraftIssuesFilter, IDraftIssues } from "store/issue/draft";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
// components // components
import { List } from "./default"; import { List } from "./default";
import { IQuickActionProps } from "./list-view-types"; import { IQuickActionProps } from "./list-view-types";
import { useIssuesActions } from "hooks/use-issues-actions";
// constants // constants
// hooks // hooks
type ListStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.ARCHIVED
| EIssuesStoreType.DRAFT
| EIssuesStoreType.PROFILE;
interface IBaseListRoot { interface IBaseListRoot {
issuesFilter:
| IProjectIssuesFilter
| IModuleIssuesFilter
| ICycleIssuesFilter
| IProjectViewIssuesFilter
| IProfileIssuesFilter
| IDraftIssuesFilter
| IArchivedIssuesFilter;
issues:
| IProjectIssues
| ICycleIssues
| IModuleIssues
| IProjectViewIssues
| IProfileIssues
| IDraftIssues
| IArchivedIssues;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.RESTORE]?: (issue: TIssue) => Promise<void>;
};
viewId?: string; viewId?: string;
storeType: TCreateModalStoreTypes; storeType: ListStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<any>; addIssuesToView?: (issueIds: string[]) => Promise<any>;
canEditPropertiesBasedOnProject?: (projectId: string) => boolean; canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
isCompletedCycle?: boolean; isCompletedCycle?: boolean;
} }
export const BaseListRoot = observer((props: IBaseListRoot) => { export const BaseListRoot = observer((props: IBaseListRoot) => {
const { const {
issuesFilter,
issues,
QuickActions, QuickActions,
issueActions,
viewId, viewId,
storeType, storeType,
addIssuesToView, addIssuesToView,
canEditPropertiesBasedOnProject, canEditPropertiesBasedOnProject,
isCompletedCycle = false, isCompletedCycle = false,
} = props; } = props;
const { issuesFilter, issues } = useIssues(storeType);
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue } = useIssuesActions(storeType);
// mobx store // mobx store
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
@ -80,7 +58,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
const isEditingAllowedBasedOnProject = const isEditingAllowedBasedOnProject =
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
return enableInlineEditing && isEditingAllowedBasedOnProject; return !!enableInlineEditing && isEditingAllowedBasedOnProject;
}, },
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed] [canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
); );
@ -91,37 +69,20 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
const group_by = displayFilters?.group_by || null; const group_by = displayFilters?.group_by || null;
const showEmptyGroup = displayFilters?.show_empty_groups ?? false; const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
const handleIssues = useCallback(
async (issue: TIssue, action: EIssueActions) => {
if (issueActions[action]) {
await issueActions[action]!(issue);
}
},
[issueActions]
);
const renderQuickActions = useCallback( const renderQuickActions = useCallback(
(issue: TIssue) => ( (issue: TIssue) => (
<QuickActions <QuickActions
issue={issue} issue={issue}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleUpdate={ handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
} handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRemoveFromView={ handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined
}
handleArchive={
issueActions[EIssueActions.ARCHIVE] ? async () => handleIssues(issue, EIssueActions.ARCHIVE) : undefined
}
handleRestore={
issueActions[EIssueActions.RESTORE] ? async () => handleIssues(issue, EIssueActions.RESTORE) : undefined
}
readOnly={!isEditingAllowed || isCompletedCycle} readOnly={!isEditingAllowed || isCompletedCycle}
/> />
), ),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[handleIssues] [isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
); );
return ( return (
@ -130,7 +91,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
issuesMap={issueMap} issuesMap={issueMap}
displayProperties={displayProperties} displayProperties={displayProperties}
group_by={group_by} group_by={group_by}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={renderQuickActions} quickActions={renderQuickActions}
issueIds={issueIds} issueIds={issueIds}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}

View File

@ -9,19 +9,18 @@ import { useApplication, useIssueDetail, useProject } from "hooks/store";
// types // types
import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types"; import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types";
import { IssueProperties } from "../properties/all-properties"; import { IssueProperties } from "../properties/all-properties";
import { EIssueActions } from "../types";
interface IssueBlockProps { interface IssueBlockProps {
issueId: string; issueId: string;
issuesMap: TIssueMap; issuesMap: TIssueMap;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue) => React.ReactNode; quickActions: (issue: TIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
} }
export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlockProps) => { export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlockProps) => {
const { issuesMap, issueId, handleIssues, quickActions, displayProperties, canEditProperties } = props; const { issuesMap, issueId, updateIssue, quickActions, displayProperties, canEditProperties } = props;
// hooks // hooks
const { const {
router: { workspaceSlug }, router: { workspaceSlug },
@ -29,10 +28,6 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
const { getProjectIdentifierById } = useProject(); const { getProjectIdentifierById } = useProject();
const { peekIssue, setPeekIssue } = useIssueDetail(); const { peekIssue, setPeekIssue } = useIssueDetail();
const updateIssue = async (issueToUpdate: TIssue) => {
await handleIssues(issueToUpdate, EIssueActions.UPDATE);
};
const handleIssuePeekOverview = (issue: TIssue) => const handleIssuePeekOverview = (issue: TIssue) =>
workspaceSlug && workspaceSlug &&
issue && issue &&
@ -91,7 +86,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
className="relative flex items-center gap-2 whitespace-nowrap" className="relative flex items-center gap-2 whitespace-nowrap"
issue={issue} issue={issue}
isReadOnly={!canEditIssueProperties} isReadOnly={!canEditIssueProperties}
handleIssues={updateIssue} updateIssue={updateIssue}
displayProperties={displayProperties} displayProperties={displayProperties}
activeLayout="List" activeLayout="List"
/> />

View File

@ -4,20 +4,19 @@ import RenderIfVisible from "components/core/render-if-visible-HOC";
import { IssueBlock } from "components/issues"; import { IssueBlock } from "components/issues";
// types // types
import { TGroupedIssues, TIssue, IIssueDisplayProperties, TIssueMap, TUnGroupedIssues } from "@plane/types"; import { TGroupedIssues, TIssue, IIssueDisplayProperties, TIssueMap, TUnGroupedIssues } from "@plane/types";
import { EIssueActions } from "../types";
interface Props { interface Props {
issueIds: TGroupedIssues | TUnGroupedIssues | any; issueIds: TGroupedIssues | TUnGroupedIssues | any;
issuesMap: TIssueMap; issuesMap: TIssueMap;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue) => React.ReactNode; quickActions: (issue: TIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
containerRef: MutableRefObject<HTMLDivElement | null>; containerRef: MutableRefObject<HTMLDivElement | null>;
} }
export const IssueBlocksList: FC<Props> = (props) => { export const IssueBlocksList: FC<Props> = (props) => {
const { issueIds, issuesMap, handleIssues, quickActions, displayProperties, canEditProperties, containerRef } = props; const { issueIds, issuesMap, updateIssue, quickActions, displayProperties, canEditProperties, containerRef } = props;
return ( return (
<div className="relative h-full w-full"> <div className="relative h-full w-full">
@ -35,7 +34,7 @@ export const IssueBlocksList: FC<Props> = (props) => {
<IssueBlock <IssueBlock
issueId={issueId} issueId={issueId}
issuesMap={issuesMap} issuesMap={issuesMap}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
displayProperties={displayProperties} displayProperties={displayProperties}

View File

@ -2,7 +2,6 @@ import { useRef } from "react";
// components // components
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues"; import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
// hooks // hooks
import { TCreateModalStoreTypes } from "constants/issue";
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store"; import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store";
// constants // constants
// types // types
@ -15,15 +14,15 @@ import {
TUnGroupedIssues, TUnGroupedIssues,
IGroupByColumn, IGroupByColumn,
} from "@plane/types"; } from "@plane/types";
import { EIssueActions } from "../types";
import { getGroupByColumns } from "../utils"; import { getGroupByColumns } from "../utils";
import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card";
import { EIssuesStoreType } from "constants/issue";
export interface IGroupByList { export interface IGroupByList {
issueIds: TGroupedIssues | TUnGroupedIssues | any; issueIds: TGroupedIssues | TUnGroupedIssues | any;
issuesMap: TIssueMap; issuesMap: TIssueMap;
group_by: string | null; group_by: string | null;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue) => React.ReactNode; quickActions: (issue: TIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
enableIssueQuickAdd: boolean; enableIssueQuickAdd: boolean;
@ -36,7 +35,7 @@ export interface IGroupByList {
viewId?: string viewId?: string
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType: TCreateModalStoreTypes; storeType: EIssuesStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
viewId?: string; viewId?: string;
isCompletedCycle?: boolean; isCompletedCycle?: boolean;
@ -47,7 +46,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
issueIds, issueIds,
issuesMap, issuesMap,
group_by, group_by,
handleIssues, updateIssue,
quickActions, quickActions,
displayProperties, displayProperties,
enableIssueQuickAdd, enableIssueQuickAdd,
@ -142,7 +141,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
<IssueBlocksList <IssueBlocksList
issueIds={is_list ? issueIds || 0 : issueIds?.[_list.id] || 0} issueIds={is_list ? issueIds || 0 : issueIds?.[_list.id] || 0}
issuesMap={issuesMap} issuesMap={issuesMap}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties} displayProperties={displayProperties}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
@ -170,7 +169,7 @@ export interface IList {
issueIds: TGroupedIssues | TUnGroupedIssues | any; issueIds: TGroupedIssues | TUnGroupedIssues | any;
issuesMap: TIssueMap; issuesMap: TIssueMap;
group_by: string | null; group_by: string | null;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: (issue: TIssue) => React.ReactNode; quickActions: (issue: TIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
showEmptyGroup: boolean; showEmptyGroup: boolean;
@ -184,7 +183,7 @@ export interface IList {
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType: TCreateModalStoreTypes; storeType: EIssuesStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
isCompletedCycle?: boolean; isCompletedCycle?: boolean;
} }
@ -194,7 +193,7 @@ export const List: React.FC<IList> = (props) => {
issueIds, issueIds,
issuesMap, issuesMap,
group_by, group_by,
handleIssues, updateIssue,
quickActions, quickActions,
quickAddCallback, quickAddCallback,
viewId, viewId,
@ -214,7 +213,7 @@ export const List: React.FC<IList> = (props) => {
issueIds={issueIds as TUnGroupedIssues} issueIds={issueIds as TUnGroupedIssues}
issuesMap={issuesMap} issuesMap={issuesMap}
group_by={group_by} group_by={group_by}
handleIssues={handleIssues} updateIssue={updateIssue}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties} displayProperties={displayProperties}
enableIssueQuickAdd={enableIssueQuickAdd} enableIssueQuickAdd={enableIssueQuickAdd}

View File

@ -10,7 +10,7 @@ import { CreateUpdateIssueModal } from "components/issues";
// ui // ui
// mobx // mobx
// hooks // hooks
import { TCreateModalStoreTypes } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useEventTracker } from "hooks/store"; import { useEventTracker } from "hooks/store";
// types // types
import { TIssue, ISearchIssueResponse } from "@plane/types"; import { TIssue, ISearchIssueResponse } from "@plane/types";
@ -21,7 +21,7 @@ interface IHeaderGroupByCard {
count: number; count: number;
issuePayload: Partial<TIssue>; issuePayload: Partial<TIssue>;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
storeType: TCreateModalStoreTypes; storeType: EIssuesStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>; addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
} }

View File

@ -1,47 +1,20 @@
import { FC, useMemo } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks // hooks
import { ArchivedIssueQuickActions } from "components/issues"; import { ArchivedIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
// constants // constants
import { EIssueActions } from "../../types";
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
export const ArchivedIssueListLayout: FC = observer(() => { export const ArchivedIssueListLayout: FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
const issueActions = useMemo(
() => ({
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.removeIssue(workspaceSlug, projectId, issue.id);
},
[EIssueActions.RESTORE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.restoreIssue(workspaceSlug, projectId, issue.id);
},
}),
[issues, workspaceSlug, projectId]
);
const canEditPropertiesBasedOnProject = () => false; const canEditPropertiesBasedOnProject = () => false;
return ( return (
<BaseListRoot <BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ArchivedIssueQuickActions} QuickActions={ArchivedIssueQuickActions}
issueActions={issueActions} storeType={EIssuesStoreType.ARCHIVED}
storeType={EIssuesStoreType.PROJECT}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject} canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/> />
); );

View File

@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from "react"; import React, { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
@ -7,9 +7,7 @@ import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store"; import { useCycle, useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
// constants // constants
import { EIssueActions } from "../../types";
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
export interface ICycleListLayout {} export interface ICycleListLayout {}
@ -18,34 +16,9 @@ export const CycleListLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
// store // store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE); const { issues } = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCompletedCycleIds } = useCycle(); const { currentProjectCompletedCycleIds } = useCycle();
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
await issues.archiveIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
},
}),
[issues, workspaceSlug, cycleId]
);
const isCompletedCycle = const isCompletedCycle =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false; cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
@ -61,10 +34,7 @@ export const CycleListLayout: React.FC = observer(() => {
return ( return (
<BaseListRoot <BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
issueActions={issueActions}
viewId={cycleId?.toString()} viewId={cycleId?.toString()}
storeType={EIssuesStoreType.CYCLE} storeType={EIssuesStoreType.CYCLE}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}

View File

@ -1,49 +1,19 @@
import { FC, useMemo } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants // constants
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
export const DraftIssueListLayout: FC = observer(() => { export const DraftIssueListLayout: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query;
if (!workspaceSlug || !projectId) return null; if (!workspaceSlug || !projectId) return null;
// store return <BaseListRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.DRAFT} />;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.updateIssue(workspaceSlug, projectId, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.removeIssue(workspaceSlug, projectId, issue.id);
},
}),
[issues, workspaceSlug, projectId]
);
return (
<BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROJECT}
/>
);
}); });

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// mobx store // mobx store
@ -7,8 +7,6 @@ import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store"; import { useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants // constants
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
@ -18,40 +16,11 @@ export const ModuleListLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE); const { issues } = useIssues(EIssuesStoreType.MODULE);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, moduleId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.removeIssueFromModule(workspaceSlug.toString(), issue.project_id, moduleId.toString(), issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
await issues.archiveIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
},
}),
[issues, workspaceSlug, moduleId]
);
return ( return (
<BaseListRoot <BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
issueActions={issueActions}
viewId={moduleId?.toString()} viewId={moduleId?.toString()}
storeType={EIssuesStoreType.MODULE} storeType={EIssuesStoreType.MODULE}
addIssuesToView={(issueIds: string[]) => { addIssuesToView={(issueIds: string[]) => {

View File

@ -1,50 +1,20 @@
import { FC, useMemo } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks // hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store"; import { useUser } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants // constants
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
export const ProfileIssuesListLayout: FC = observer(() => { export const ProfileIssuesListLayout: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string };
// store hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROFILE);
const { const {
membership: { currentWorkspaceAllProjectsRole }, membership: { currentWorkspaceAllProjectsRole },
} = useUser(); } = useUser();
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, userId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id, userId);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.archiveIssue(workspaceSlug, issue.project_id, issue.id, userId);
},
}),
[issues, workspaceSlug, userId]
);
const canEditPropertiesBasedOnProject = (projectId: string) => { const canEditPropertiesBasedOnProject = (projectId: string) => {
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId]; const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
@ -53,10 +23,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
return ( return (
<BaseListRoot <BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROFILE} storeType={EIssuesStoreType.PROFILE}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject} canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/> />

View File

@ -1,55 +1,19 @@
import { FC, useMemo } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants // constants
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
export const ListLayout: FC = observer(() => { export const ListLayout: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query;
if (!workspaceSlug || !projectId) return null; if (!workspaceSlug || !projectId) return null;
// store return <BaseListRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />;
const { issuesFilter, issues } = useIssues(EIssuesStoreType.PROJECT);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.updateIssue(workspaceSlug, projectId, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.removeIssue(workspaceSlug, projectId, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.archiveIssue(workspaceSlug, projectId, issue.id);
},
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[issues]
);
return (
<BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROJECT}
/>
);
}); });

View File

@ -3,29 +3,13 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// store // store
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// constants // constants
// types // types
import { TIssue } from "@plane/types";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
// components // components
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
export interface IViewListLayout { export const ProjectViewListLayout: React.FC = observer(() => {
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
};
}
export const ProjectViewListLayout: React.FC<IViewListLayout> = observer((props) => {
const { issueActions } = props;
// store
const { issuesFilter, issues } = useIssues(EIssuesStoreType.PROJECT_VIEW);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query; const { workspaceSlug, projectId, viewId } = router.query;
@ -33,10 +17,7 @@ export const ProjectViewListLayout: React.FC<IViewListLayout> = observer((props)
return ( return (
<BaseListRoot <BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROJECT_VIEW} storeType={EIssuesStoreType.PROJECT_VIEW}
viewId={viewId?.toString()} viewId={viewId?.toString()}
/> />

View File

@ -30,7 +30,7 @@ import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-
export interface IIssueProperties { export interface IIssueProperties {
issue: TIssue; issue: TIssue;
handleIssues: (issue: TIssue) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
isReadOnly: boolean; isReadOnly: boolean;
className: string; className: string;
@ -38,7 +38,7 @@ export interface IIssueProperties {
} }
export const IssueProperties: React.FC<IIssueProperties> = observer((props) => { export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
const { issue, handleIssues, displayProperties, activeLayout, isReadOnly, className } = props; const { issue, updateIssue, displayProperties, activeLayout, isReadOnly, className } = props;
// store hooks // store hooks
const { labelMap } = useLabel(); const { labelMap } = useLabel();
const { captureIssueEvent } = useEventTracker(); const { captureIssueEvent } = useEventTracker();
@ -80,59 +80,63 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
); );
const handleState = (stateId: string) => { const handleState = (stateId: string) => {
handleIssues({ ...issue, state_id: stateId }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { state_id: stateId }).then(() => {
eventName: ISSUE_UPDATED, captureIssueEvent({
payload: { ...issue, state: "SUCCESS", element: currentLayout }, eventName: ISSUE_UPDATED,
path: router.asPath, payload: { ...issue, state: "SUCCESS", element: currentLayout },
updates: { path: router.asPath,
changed_property: "state", updates: {
change_details: stateId, changed_property: "state",
}, change_details: stateId,
},
});
}); });
});
}; };
const handlePriority = (value: TIssuePriorities) => { const handlePriority = (value: TIssuePriorities) => {
handleIssues({ ...issue, priority: value }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { priority: value }).then(() => {
eventName: ISSUE_UPDATED, captureIssueEvent({
payload: { ...issue, state: "SUCCESS", element: currentLayout }, eventName: ISSUE_UPDATED,
path: router.asPath, payload: { ...issue, state: "SUCCESS", element: currentLayout },
updates: { path: router.asPath,
changed_property: "priority", updates: {
change_details: value, changed_property: "priority",
}, change_details: value,
},
});
}); });
});
}; };
const handleLabel = (ids: string[]) => { const handleLabel = (ids: string[]) => {
handleIssues({ ...issue, label_ids: ids }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { label_ids: ids }).then(() => {
eventName: ISSUE_UPDATED, captureIssueEvent({
payload: { ...issue, state: "SUCCESS", element: currentLayout }, eventName: ISSUE_UPDATED,
path: router.asPath, payload: { ...issue, state: "SUCCESS", element: currentLayout },
updates: { path: router.asPath,
changed_property: "labels", updates: {
change_details: ids, changed_property: "labels",
}, change_details: ids,
},
});
}); });
});
}; };
const handleAssignee = (ids: string[]) => { const handleAssignee = (ids: string[]) => {
handleIssues({ ...issue, assignee_ids: ids }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { assignee_ids: ids }).then(() => {
eventName: ISSUE_UPDATED, captureIssueEvent({
payload: { ...issue, state: "SUCCESS", element: currentLayout }, eventName: ISSUE_UPDATED,
path: router.asPath, payload: { ...issue, state: "SUCCESS", element: currentLayout },
updates: { path: router.asPath,
changed_property: "assignees", updates: {
change_details: ids, changed_property: "assignees",
}, change_details: ids,
},
});
}); });
});
}; };
const handleModule = useCallback( const handleModule = useCallback(
@ -175,45 +179,52 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
); );
const handleStartDate = (date: Date | null) => { const handleStartDate = (date: Date | null) => {
handleIssues({ ...issue, start_date: date ? renderFormattedPayloadDate(date) : null }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { start_date: date ? renderFormattedPayloadDate(date) : null }).then(
eventName: ISSUE_UPDATED, () => {
payload: { ...issue, state: "SUCCESS", element: currentLayout }, captureIssueEvent({
path: router.asPath, eventName: ISSUE_UPDATED,
updates: { payload: { ...issue, state: "SUCCESS", element: currentLayout },
changed_property: "start_date", path: router.asPath,
change_details: date ? renderFormattedPayloadDate(date) : null, updates: {
}, changed_property: "start_date",
}); change_details: date ? renderFormattedPayloadDate(date) : null,
}); },
});
}
);
}; };
const handleTargetDate = (date: Date | null) => { const handleTargetDate = (date: Date | null) => {
handleIssues({ ...issue, target_date: date ? renderFormattedPayloadDate(date) : null }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { target_date: date ? renderFormattedPayloadDate(date) : null }).then(
eventName: ISSUE_UPDATED, () => {
payload: { ...issue, state: "SUCCESS", element: currentLayout }, captureIssueEvent({
path: router.asPath, eventName: ISSUE_UPDATED,
updates: { payload: { ...issue, state: "SUCCESS", element: currentLayout },
changed_property: "target_date", path: router.asPath,
change_details: date ? renderFormattedPayloadDate(date) : null, updates: {
}, changed_property: "target_date",
}); change_details: date ? renderFormattedPayloadDate(date) : null,
}); },
});
}
);
}; };
const handleEstimate = (value: number | null) => { const handleEstimate = (value: number | null) => {
handleIssues({ ...issue, estimate_point: value }).then(() => { updateIssue &&
captureIssueEvent({ updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
eventName: ISSUE_UPDATED, captureIssueEvent({
payload: { ...issue, state: "SUCCESS", element: currentLayout }, eventName: ISSUE_UPDATED,
path: router.asPath, payload: { ...issue, state: "SUCCESS", element: currentLayout },
updates: { path: router.asPath,
changed_property: "estimate_point", updates: {
change_details: value, changed_property: "estimate_point",
}, change_details: value,
},
});
}); });
});
}; };
const redirectToIssueDetail = () => { const redirectToIssueDetail = () => {

View File

@ -90,7 +90,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
}} }}
data={issueToEdit ?? duplicateIssuePayload} data={issueToEdit ?? duplicateIssuePayload}
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) await handleUpdate(data);
}} }}
storeType={EIssuesStoreType.PROJECT} storeType={EIssuesStoreType.PROJECT}
/> />

View File

@ -101,7 +101,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
}} }}
data={issueToEdit ?? duplicateIssuePayload} data={issueToEdit ?? duplicateIssuePayload}
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) await handleUpdate(data);
}} }}
storeType={EIssuesStoreType.CYCLE} storeType={EIssuesStoreType.CYCLE}
/> />

View File

@ -100,7 +100,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
}} }}
data={issueToEdit ?? duplicateIssuePayload} data={issueToEdit ?? duplicateIssuePayload}
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) await handleUpdate(data);
}} }}
storeType={EIssuesStoreType.MODULE} storeType={EIssuesStoreType.MODULE}
/> />

View File

@ -100,7 +100,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
}} }}
data={issueToEdit ?? duplicateIssuePayload} data={issueToEdit ?? duplicateIssuePayload}
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) await handleUpdate(data);
}} }}
storeType={EIssuesStoreType.PROJECT} storeType={EIssuesStoreType.PROJECT}
isDraft={isDraftIssue} isDraft={isDraftIssue}

View File

@ -1,4 +1,4 @@
import React, { Fragment, useCallback, useMemo } from "react"; import React, { Fragment, useCallback } from "react";
import isEmpty from "lodash/isEmpty"; import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -6,6 +6,7 @@ import useSWR from "swr";
// hooks // hooks
import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties"; import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties";
import { useApplication, useEventTracker, useGlobalView, useIssues, useProject, useUser } from "hooks/store"; import { useApplication, useEventTracker, useGlobalView, useIssues, useProject, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// components // components
import { GlobalViewsAppliedFiltersRoot, IssuePeekOverview } from "components/issues"; import { GlobalViewsAppliedFiltersRoot, IssuePeekOverview } from "components/issues";
import { SpreadsheetView } from "components/issues/issue-layouts"; import { SpreadsheetView } from "components/issues/issue-layouts";
@ -14,7 +15,6 @@ import { EmptyState } from "components/empty-state";
import { SpreadsheetLayoutLoader } from "components/ui"; import { SpreadsheetLayoutLoader } from "components/ui";
// types // types
import { TIssue, IIssueDisplayFilterOptions } from "@plane/types"; import { TIssue, IIssueDisplayFilterOptions } from "@plane/types";
import { EIssueActions } from "../types";
// constants // constants
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
@ -30,8 +30,9 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
const { commandPalette: commandPaletteStore } = useApplication(); const { commandPalette: commandPaletteStore } = useApplication();
const { const {
issuesFilter: { filters, fetchFilters, updateFilters }, issuesFilter: { filters, fetchFilters, updateFilters },
issues: { loader, groupedIssueIds, fetchIssues, updateIssue, removeIssue, archiveIssue }, issues: { loader, groupedIssueIds, fetchIssues },
} = useIssues(EIssuesStoreType.GLOBAL); } = useIssues(EIssuesStoreType.GLOBAL);
const { updateIssue, removeIssue, archiveIssue } = useIssuesActions(EIssuesStoreType.GLOBAL);
const { dataViewId, issueIds } = groupedIssueIds; const { dataViewId, issueIds } = groupedIssueIds;
const { const {
@ -111,41 +112,6 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined; const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined;
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
const projectId = issue.project_id;
if (!workspaceSlug || !projectId || !globalViewId) return;
await updateIssue(workspaceSlug.toString(), projectId, issue.id, issue, globalViewId.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
const projectId = issue.project_id;
if (!workspaceSlug || !projectId || !globalViewId) return;
await removeIssue(workspaceSlug.toString(), projectId, issue.id, globalViewId.toString());
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
const projectId = issue.project_id;
if (!workspaceSlug || !projectId || !globalViewId) return;
await archiveIssue(workspaceSlug.toString(), projectId, issue.id, globalViewId.toString());
},
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[updateIssue, removeIssue, workspaceSlug]
);
const handleIssues = useCallback(
async (issue: TIssue, action: EIssueActions) => {
if (action === EIssueActions.UPDATE) await issueActions[action]!(issue);
if (action === EIssueActions.DELETE) await issueActions[action]!(issue);
if (action === EIssueActions.ARCHIVE) await issueActions[action]!(issue);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const handleDisplayFiltersUpdate = useCallback( const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !globalViewId) return; if (!workspaceSlug || !globalViewId) return;
@ -166,14 +132,14 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
<AllIssueQuickActions <AllIssueQuickActions
customActionButton={customActionButton} customActionButton={customActionButton}
issue={issue} issue={issue}
handleUpdate={async () => handleIssues({ ...issue }, EIssueActions.UPDATE)} handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
handleArchive={async () => handleIssues(issue, EIssueActions.ARCHIVE)} handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
portalElement={portalElement} portalElement={portalElement}
readOnly={!canEditProperties(issue.project_id)} readOnly={!canEditProperties(issue.project_id)}
/> />
), ),
[canEditProperties, handleIssues] [canEditProperties, removeIssue, updateIssue, archiveIssue]
); );
if (loader === "init-loader" || !globalViewId || globalViewId !== dataViewId || !issueIds) { if (loader === "init-loader" || !globalViewId || globalViewId !== dataViewId || !issueIds) {
@ -213,7 +179,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
handleDisplayFilterUpdate={handleDisplayFiltersUpdate} handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issueIds={issueIds} issueIds={issueIds}
quickActions={renderQuickActions} quickActions={renderQuickActions}
handleIssues={handleIssues} updateIssue={updateIssue}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
viewId={globalViewId} viewId={globalViewId}
/> />

View File

@ -1,4 +1,4 @@
import React, { Fragment, useMemo } from "react"; import React, { Fragment } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
@ -19,8 +19,6 @@ import { ActiveLoader } from "components/ui";
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store"; import { useIssues } from "hooks/store";
// types // types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
export const ProjectViewLayoutRoot: React.FC = observer(() => { export const ProjectViewLayoutRoot: React.FC = observer(() => {
// router // router
@ -45,22 +43,6 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false } { revalidateIfStale: false, revalidateOnFocus: false }
); );
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, issue, viewId?.toString());
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !projectId) return;
await issues.removeIssue(workspaceSlug.toString(), projectId.toString(), issue.id, viewId?.toString());
},
}),
[issues, workspaceSlug, projectId, viewId]
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
if (!workspaceSlug || !projectId || !viewId) return <></>; if (!workspaceSlug || !projectId || !viewId) return <></>;
@ -81,15 +63,15 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
<Fragment> <Fragment>
<div className="relative h-full w-full overflow-auto"> <div className="relative h-full w-full overflow-auto">
{activeLayout === "list" ? ( {activeLayout === "list" ? (
<ProjectViewListLayout issueActions={issueActions} /> <ProjectViewListLayout />
) : activeLayout === "kanban" ? ( ) : activeLayout === "kanban" ? (
<ProjectViewKanBanLayout issueActions={issueActions} /> <ProjectViewKanBanLayout />
) : activeLayout === "calendar" ? ( ) : activeLayout === "calendar" ? (
<ProjectViewCalendarLayout issueActions={issueActions} /> <ProjectViewCalendarLayout />
) : activeLayout === "gantt_chart" ? ( ) : activeLayout === "gantt_chart" ? (
<ProjectViewGanttLayout issueActions={issueActions} /> <ProjectViewGanttLayout />
) : activeLayout === "spreadsheet" ? ( ) : activeLayout === "spreadsheet" ? (
<ProjectViewSpreadsheetLayout issueActions={issueActions} /> <ProjectViewSpreadsheetLayout />
) : null} ) : null}
</div> </div>

View File

@ -2,56 +2,44 @@ import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { EIssueFilterType } from "constants/issue"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// views // views
// types // types
// constants // constants
import { ICycleIssuesFilter, ICycleIssues } from "store/issue/cycle";
import { IModuleIssuesFilter, IModuleIssues } from "store/issue/module";
import { IProjectIssuesFilter, IProjectIssues } from "store/issue/project";
import { IProjectViewIssuesFilter, IProjectViewIssues } from "store/issue/project-views";
import { TIssue, IIssueDisplayFilterOptions, TUnGroupedIssues } from "@plane/types"; import { TIssue, IIssueDisplayFilterOptions, TUnGroupedIssues } from "@plane/types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EIssueActions } from "../types";
import { SpreadsheetView } from "./spreadsheet-view"; import { SpreadsheetView } from "./spreadsheet-view";
export type SpreadsheetStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW;
interface IBaseSpreadsheetRoot { interface IBaseSpreadsheetRoot {
issueFiltersStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
issueStore: IProjectIssues | ICycleIssues | IModuleIssues | IProjectViewIssues;
viewId?: string; viewId?: string;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: { storeType: SpreadsheetStoreType;
[EIssueActions.DELETE]: (issue: TIssue) => void;
[EIssueActions.UPDATE]?: (issue: TIssue) => void;
[EIssueActions.REMOVE]?: (issue: TIssue) => void;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => void;
[EIssueActions.RESTORE]?: (issue: TIssue) => Promise<void>;
};
canEditPropertiesBasedOnProject?: (projectId: string) => boolean; canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
isCompletedCycle?: boolean; isCompletedCycle?: boolean;
} }
export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
const { const { viewId, QuickActions, storeType, canEditPropertiesBasedOnProject, isCompletedCycle = false } = props;
issueFiltersStore,
issueStore,
viewId,
QuickActions,
issueActions,
canEditPropertiesBasedOnProject,
isCompletedCycle = false,
} = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { projectId } = router.query;
// store hooks // store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { issues, issuesFilter } = useIssues(storeType);
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
useIssuesActions(storeType);
// derived values // derived values
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {}; const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {};
// user role validation // user role validation
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@ -65,32 +53,17 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed] [canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
); );
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues; const issueIds = (issues.groupedIssueIds ?? []) as TUnGroupedIssues;
const handleIssues = useCallback(
async (issue: TIssue, action: EIssueActions) => {
if (issueActions[action]) {
issueActions[action]!(issue);
}
},
[issueActions]
);
const handleDisplayFiltersUpdate = useCallback( const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return; if ( !projectId) return;
issueFiltersStore.updateFilters( updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
workspaceSlug, ...updatedDisplayFilter,
projectId, });
EIssueFilterType.DISPLAY_FILTERS,
{
...updatedDisplayFilter,
},
viewId
);
}, },
[issueFiltersStore, projectId, workspaceSlug, viewId] [ projectId, updateFilters]
); );
const renderQuickActions = useCallback( const renderQuickActions = useCallback(
@ -98,37 +71,28 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
<QuickActions <QuickActions
customActionButton={customActionButton} customActionButton={customActionButton}
issue={issue} issue={issue}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleUpdate={ handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
} handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRemoveFromView={ handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined
}
handleArchive={
issueActions[EIssueActions.ARCHIVE] ? async () => handleIssues(issue, EIssueActions.ARCHIVE) : undefined
}
handleRestore={
issueActions[EIssueActions.RESTORE] ? async () => handleIssues(issue, EIssueActions.RESTORE) : undefined
}
portalElement={portalElement} portalElement={portalElement}
readOnly={!isEditingAllowed || isCompletedCycle} readOnly={!isEditingAllowed || isCompletedCycle}
/> />
), ),
// eslint-disable-next-line react-hooks/exhaustive-deps [isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
[handleIssues]
); );
return ( return (
<SpreadsheetView <SpreadsheetView
displayProperties={issueFiltersStore.issueFilters?.displayProperties ?? {}} displayProperties={issuesFilter.issueFilters?.displayProperties ?? {}}
displayFilters={issueFiltersStore.issueFilters?.displayFilters ?? {}} displayFilters={issuesFilter.issueFilters?.displayFilters ?? {}}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate} handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issueIds={issueIds} issueIds={issueIds}
quickActions={renderQuickActions} quickActions={renderQuickActions}
handleIssues={handleIssues} updateIssue={updateIssue}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
quickAddCallback={issueStore.quickAddIssue} quickAddCallback={issues.quickAddIssue}
viewId={viewId} viewId={viewId}
enableQuickCreateIssue={enableQuickAdd} enableQuickCreateIssue={enableQuickAdd}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle} disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}

View File

@ -6,7 +6,6 @@ import { SPREADSHEET_PROPERTY_DETAILS } from "constants/spreadsheet";
import { useEventTracker } from "hooks/store"; import { useEventTracker } from "hooks/store";
import { IIssueDisplayProperties, TIssue } from "@plane/types"; import { IIssueDisplayProperties, TIssue } from "@plane/types";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { EIssueActions } from "../types";
// constants // constants
// components // components
@ -15,12 +14,12 @@ type Props = {
issueDetail: TIssue; issueDetail: TIssue;
disableUserActions: boolean; disableUserActions: boolean;
property: keyof IIssueDisplayProperties; property: keyof IIssueDisplayProperties;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
isEstimateEnabled: boolean; isEstimateEnabled: boolean;
}; };
export const IssueColumn = observer((props: Props) => { export const IssueColumn = observer((props: Props) => {
const { displayProperties, issueDetail, disableUserActions, property, handleIssues, isEstimateEnabled } = props; const { displayProperties, issueDetail, disableUserActions, property, updateIssue, isEstimateEnabled } = props;
// router // router
const router = useRouter(); const router = useRouter();
const tableCellRef = useRef<HTMLTableCellElement | null>(null); const tableCellRef = useRef<HTMLTableCellElement | null>(null);
@ -44,7 +43,8 @@ export const IssueColumn = observer((props: Props) => {
<Column <Column
issue={issueDetail} issue={issueDetail}
onChange={(issue: TIssue, data: Partial<TIssue>, updates: any) => onChange={(issue: TIssue, data: Partial<TIssue>, updates: any) =>
handleIssues({ ...issue, ...data }, EIssueActions.UPDATE).then(() => { updateIssue &&
updateIssue(issue.project_id, issue.id, data).then(() => {
captureIssueEvent({ captureIssueEvent({
eventName: "Issue updated", eventName: "Issue updated",
payload: { payload: {

View File

@ -18,7 +18,6 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
import { IIssueDisplayProperties, TIssue } from "@plane/types"; import { IIssueDisplayProperties, TIssue } from "@plane/types";
// local components // local components
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { EIssueActions } from "../types";
import { IssueColumn } from "./issue-column"; import { IssueColumn } from "./issue-column";
interface Props { interface Props {
@ -30,7 +29,7 @@ interface Props {
portalElement?: HTMLDivElement | null portalElement?: HTMLDivElement | null
) => React.ReactNode; ) => React.ReactNode;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
portalElement: React.MutableRefObject<HTMLDivElement | null>; portalElement: React.MutableRefObject<HTMLDivElement | null>;
nestingLevel: number; nestingLevel: number;
issueId: string; issueId: string;
@ -46,7 +45,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
isEstimateEnabled, isEstimateEnabled,
nestingLevel, nestingLevel,
portalElement, portalElement,
handleIssues, updateIssue,
quickActions, quickActions,
canEditProperties, canEditProperties,
isScrolled, isScrolled,
@ -76,7 +75,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
nestingLevel={nestingLevel} nestingLevel={nestingLevel}
isEstimateEnabled={isEstimateEnabled} isEstimateEnabled={isEstimateEnabled}
handleIssues={handleIssues} updateIssue={updateIssue}
portalElement={portalElement} portalElement={portalElement}
isScrolled={isScrolled} isScrolled={isScrolled}
isExpanded={isExpanded} isExpanded={isExpanded}
@ -96,7 +95,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
nestingLevel={nestingLevel + 1} nestingLevel={nestingLevel + 1}
isEstimateEnabled={isEstimateEnabled} isEstimateEnabled={isEstimateEnabled}
handleIssues={handleIssues} updateIssue={updateIssue}
portalElement={portalElement} portalElement={portalElement}
isScrolled={isScrolled} isScrolled={isScrolled}
containerRef={containerRef} containerRef={containerRef}
@ -116,7 +115,7 @@ interface IssueRowDetailsProps {
portalElement?: HTMLDivElement | null portalElement?: HTMLDivElement | null
) => React.ReactNode; ) => React.ReactNode;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
portalElement: React.MutableRefObject<HTMLDivElement | null>; portalElement: React.MutableRefObject<HTMLDivElement | null>;
nestingLevel: number; nestingLevel: number;
issueId: string; issueId: string;
@ -132,7 +131,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
isEstimateEnabled, isEstimateEnabled,
nestingLevel, nestingLevel,
portalElement, portalElement,
handleIssues, updateIssue,
quickActions, quickActions,
canEditProperties, canEditProperties,
isScrolled, isScrolled,
@ -261,7 +260,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
issueDetail={issueDetail} issueDetail={issueDetail}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
property={property} property={property}
handleIssues={handleIssues} updateIssue={updateIssue}
isEstimateEnabled={isEstimateEnabled} isEstimateEnabled={isEstimateEnabled}
/> />
))} ))}

View File

@ -1,59 +1,32 @@
import React, { useCallback, useMemo } from "react"; import React, { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// mobx store // mobx store
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store"; import { useCycle } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { CycleIssueQuickActions } from "../../quick-action-dropdowns"; import { CycleIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const CycleSpreadsheetLayout: React.FC = observer(() => { export const CycleSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; const { cycleId } = router.query;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCompletedCycleIds } = useCycle(); const { currentProjectCompletedCycleIds } = useCycle();
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, cycleId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
issues.removeIssue(workspaceSlug, issue.project_id, issue.id, cycleId);
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
issues.removeIssueFromCycle(workspaceSlug, issue.project_id, cycleId, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !cycleId) return;
issues.archiveIssue(workspaceSlug, issue.project_id, issue.id, cycleId);
},
}),
[issues, workspaceSlug, cycleId]
);
const isCompletedCycle = const isCompletedCycle =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false; cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
const canEditIssueProperties = useCallback(() => !isCompletedCycle, [isCompletedCycle]); const canEditIssueProperties = useCallback(() => !isCompletedCycle, [isCompletedCycle]);
if (!cycleId) return null;
return ( return (
<BaseSpreadsheetRoot <BaseSpreadsheetRoot
issueStore={issues} viewId={cycleId?.toString()}
issueFiltersStore={issuesFilter}
viewId={cycleId}
issueActions={issueActions}
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
canEditPropertiesBasedOnProject={canEditIssueProperties} canEditPropertiesBasedOnProject={canEditIssueProperties}
isCompletedCycle={isCompletedCycle} isCompletedCycle={isCompletedCycle}
storeType={EIssuesStoreType.CYCLE}
/> />
); );
}); });

View File

@ -1,51 +1,23 @@
import React, { useMemo } from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// mobx store // mobx store
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { ModuleIssueQuickActions } from "../../quick-action-dropdowns"; import { ModuleIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const ModuleSpreadsheetLayout: React.FC = observer(() => { export const ModuleSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string }; const { moduleId } = router.query;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE); if (!moduleId) return null;
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, moduleId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
issues.removeIssue(workspaceSlug, issue.project_id, issue.id, moduleId);
},
[EIssueActions.REMOVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
issues.removeIssueFromModule(workspaceSlug, issue.project_id, moduleId, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug || !moduleId) return;
issues.archiveIssue(workspaceSlug, issue.project_id, issue.id, moduleId);
},
}),
[issues, workspaceSlug, moduleId]
);
return ( return (
<BaseSpreadsheetRoot <BaseSpreadsheetRoot
issueStore={issues} viewId={moduleId.toString()}
issueFiltersStore={issuesFilter}
viewId={moduleId}
issueActions={issueActions}
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
storeType={EIssuesStoreType.MODULE}
/> />
); );
}); });

View File

@ -1,48 +1,10 @@
import React, { useMemo } from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx store // mobx store
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
import { TIssue } from "@plane/types";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const ProjectSpreadsheetLayout: React.FC = observer(() => { export const ProjectSpreadsheetLayout: React.FC = observer(() => (
const router = useRouter(); <BaseSpreadsheetRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
const { workspaceSlug } = router.query as { workspaceSlug: string }; ));
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id);
},
[EIssueActions.ARCHIVE]: async (issue: TIssue) => {
if (!workspaceSlug) return;
await issues.archiveIssue(workspaceSlug, issue.project_id, issue.id);
},
}),
[issues, workspaceSlug]
);
return (
<BaseSpreadsheetRoot
issueStore={issues}
issueFiltersStore={issuesFilter}
issueActions={issueActions}
QuickActions={ProjectIssueQuickActions}
/>
);
});

View File

@ -3,39 +3,22 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// mobx store // mobx store
import { EIssuesStoreType } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components // components
import { TIssue } from "@plane/types";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
// types // types
// constants // constants
export interface IViewSpreadsheetLayout { export const ProjectViewSpreadsheetLayout: React.FC = observer(() => {
issueActions: {
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
[EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise<void>;
};
}
export const ProjectViewSpreadsheetLayout: React.FC<IViewSpreadsheetLayout> = observer((props) => {
const { issueActions } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { viewId } = router.query; const { viewId } = router.query;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
return ( return (
<BaseSpreadsheetRoot <BaseSpreadsheetRoot
issueStore={issues}
issueFiltersStore={issuesFilter}
issueActions={issueActions}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
viewId={viewId?.toString()} viewId={viewId?.toString()}
storeType={EIssuesStoreType.PROJECT_VIEW}
/> />
); );
}); });

View File

@ -3,7 +3,6 @@ import { observer } from "mobx-react-lite";
//types //types
import { useTableKeyboardNavigation } from "hooks/use-table-keyboard-navigation"; import { useTableKeyboardNavigation } from "hooks/use-table-keyboard-navigation";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssue } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssue } from "@plane/types";
import { EIssueActions } from "../types";
//components //components
import { SpreadsheetIssueRow } from "./issue-row"; import { SpreadsheetIssueRow } from "./issue-row";
import { SpreadsheetHeader } from "./spreadsheet-header"; import { SpreadsheetHeader } from "./spreadsheet-header";
@ -19,7 +18,7 @@ type Props = {
customActionButton?: React.ReactElement, customActionButton?: React.ReactElement,
portalElement?: HTMLDivElement | null portalElement?: HTMLDivElement | null
) => React.ReactNode; ) => React.ReactNode;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
portalElement: React.MutableRefObject<HTMLDivElement | null>; portalElement: React.MutableRefObject<HTMLDivElement | null>;
containerRef: MutableRefObject<HTMLTableElement | null>; containerRef: MutableRefObject<HTMLTableElement | null>;
@ -34,7 +33,7 @@ export const SpreadsheetTable = observer((props: Props) => {
isEstimateEnabled, isEstimateEnabled,
portalElement, portalElement,
quickActions, quickActions,
handleIssues, updateIssue,
canEditProperties, canEditProperties,
containerRef, containerRef,
} = props; } = props;
@ -95,7 +94,7 @@ export const SpreadsheetTable = observer((props: Props) => {
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
nestingLevel={0} nestingLevel={0}
isEstimateEnabled={isEstimateEnabled} isEstimateEnabled={isEstimateEnabled}
handleIssues={handleIssues} updateIssue={updateIssue}
portalElement={portalElement} portalElement={portalElement}
containerRef={containerRef} containerRef={containerRef}
isScrolled={isScrolled} isScrolled={isScrolled}

View File

@ -5,7 +5,6 @@ import { Spinner } from "@plane/ui";
import { SpreadsheetQuickAddIssueForm } from "components/issues"; import { SpreadsheetQuickAddIssueForm } from "components/issues";
import { useProject } from "hooks/store"; import { useProject } from "hooks/store";
import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types"; import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
import { EIssueActions } from "../types";
import { SpreadsheetTable } from "./spreadsheet-table"; import { SpreadsheetTable } from "./spreadsheet-table";
// types // types
//hooks //hooks
@ -20,7 +19,7 @@ type Props = {
customActionButton?: React.ReactElement, customActionButton?: React.ReactElement,
portalElement?: HTMLDivElement | null portalElement?: HTMLDivElement | null
) => React.ReactNode; ) => React.ReactNode;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>; updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
openIssuesListModal?: (() => void) | null; openIssuesListModal?: (() => void) | null;
quickAddCallback?: ( quickAddCallback?: (
workspaceSlug: string, workspaceSlug: string,
@ -41,7 +40,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
handleDisplayFilterUpdate, handleDisplayFilterUpdate,
issueIds, issueIds,
quickActions, quickActions,
handleIssues, updateIssue,
quickAddCallback, quickAddCallback,
viewId, viewId,
canEditProperties, canEditProperties,
@ -75,7 +74,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
isEstimateEnabled={isEstimateEnabled} isEstimateEnabled={isEstimateEnabled}
portalElement={portalRef} portalElement={portalRef}
quickActions={quickActions} quickActions={quickActions}
handleIssues={handleIssues} updateIssue={updateIssue}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
containerRef={containerRef} containerRef={containerRef}
/> />

View File

@ -29,6 +29,7 @@ import { FileService } from "services/file.service";
// components // components
// ui // ui
// helpers // helpers
import { getChangedIssuefields } from "helpers/issue.helper";
// types // types
import type { TIssue, ISearchIssueResponse } from "@plane/types"; import type { TIssue, ISearchIssueResponse } from "@plane/types";
@ -126,7 +127,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
} = useIssueDetail(); } = useIssueDetail();
// form info // form info
const { const {
formState: { errors, isDirty, isSubmitting }, formState: { errors, isDirty, isSubmitting, dirtyFields },
handleSubmit, handleSubmit,
reset, reset,
watch, watch,
@ -166,7 +167,15 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const issueName = watch("name"); const issueName = watch("name");
const handleFormSubmit = async (formData: Partial<TIssue>, is_draft_issue = false) => { const handleFormSubmit = async (formData: Partial<TIssue>, is_draft_issue = false) => {
await onSubmit(formData, is_draft_issue); const submitData = !data?.id
? formData
: {
...getChangedIssuefields(formData, dirtyFields as { [key: string]: boolean | undefined }),
project_id: getValues("project_id"),
id: data.id,
description_html: formData.description_html ?? "<p></p>",
};
await onSubmit(submitData, is_draft_issue);
setGptAssistantModal(false); setGptAssistantModal(false);
@ -761,3 +770,4 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
</> </>
); );
}); });

View File

@ -6,7 +6,7 @@ import { Dialog, Transition } from "@headlessui/react";
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
import { ISSUE_CREATED, ISSUE_UPDATED } from "constants/event-tracker"; import { ISSUE_CREATED, ISSUE_UPDATED } from "constants/event-tracker";
import { EIssuesStoreType, TCreateModalStoreTypes } from "constants/issue"; import { EIssuesStoreType } from "constants/issue";
import { import {
useApplication, useApplication,
useEventTracker, useEventTracker,
@ -17,6 +17,7 @@ import {
useIssueDetail, useIssueDetail,
} from "hooks/store"; } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
import { useIssuesActions } from "hooks/use-issues-actions";
// components // components
import type { TIssue } from "@plane/types"; import type { TIssue } from "@plane/types";
import { DraftIssueLayout } from "./draft-issue-layout"; import { DraftIssueLayout } from "./draft-issue-layout";
@ -31,7 +32,7 @@ export interface IssuesModalProps {
onClose: () => void; onClose: () => void;
onSubmit?: (res: TIssue) => Promise<void>; onSubmit?: (res: TIssue) => Promise<void>;
withDraftIssueWrapper?: boolean; withDraftIssueWrapper?: boolean;
storeType?: TCreateModalStoreTypes; storeType?: EIssuesStoreType;
isDraft?: boolean; isDraft?: boolean;
} }
@ -53,41 +54,15 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
// store hooks // store hooks
const { captureIssueEvent } = useEventTracker(); const { captureIssueEvent } = useEventTracker();
const { const {
router: { workspaceSlug, projectId, cycleId, moduleId, viewId: projectViewId }, router: { workspaceSlug, projectId, cycleId, moduleId },
} = useApplication(); } = useApplication();
const { workspaceProjectIds } = useProject(); const { workspaceProjectIds } = useProject();
const { fetchCycleDetails } = useCycle(); const { fetchCycleDetails } = useCycle();
const { fetchModuleDetails } = useModule(); const { fetchModuleDetails } = useModule();
const { issues: projectIssues } = useIssues(EIssuesStoreType.PROJECT);
const { issues: moduleIssues } = useIssues(EIssuesStoreType.MODULE); const { issues: moduleIssues } = useIssues(EIssuesStoreType.MODULE);
const { issues: cycleIssues } = useIssues(EIssuesStoreType.CYCLE); const { issues: cycleIssues } = useIssues(EIssuesStoreType.CYCLE);
const { issues: viewIssues } = useIssues(EIssuesStoreType.PROJECT_VIEW);
const { issues: profileIssues } = useIssues(EIssuesStoreType.PROFILE);
const { issues: draftIssues } = useIssues(EIssuesStoreType.DRAFT); const { issues: draftIssues } = useIssues(EIssuesStoreType.DRAFT);
const { fetchIssue } = useIssueDetail(); const { fetchIssue } = useIssueDetail();
// store mapping based on current store
const issueStores = {
[EIssuesStoreType.PROJECT]: {
store: projectIssues,
viewId: undefined,
},
[EIssuesStoreType.PROJECT_VIEW]: {
store: viewIssues,
viewId: projectViewId,
},
[EIssuesStoreType.PROFILE]: {
store: profileIssues,
viewId: undefined,
},
[EIssuesStoreType.CYCLE]: {
store: cycleIssues,
viewId: cycleId,
},
[EIssuesStoreType.MODULE]: {
store: moduleIssues,
viewId: moduleId,
},
};
// router // router
const router = useRouter(); const router = useRouter();
// local storage // local storage
@ -95,7 +70,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
Record<string, Partial<TIssue>> Record<string, Partial<TIssue>>
>("draftedIssue", {}); >("draftedIssue", {});
// current store details // current store details
const { store: currentIssueStore, viewId } = issueStores[storeType]; const { createIssue, updateIssue } = useIssuesActions(storeType);
const fetchIssueDetail = async (issueId: string | undefined) => { const fetchIssueDetail = async (issueId: string | undefined) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -176,11 +151,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
try { try {
const response = is_draft_issue const response = is_draft_issue
? await draftIssues.createIssue(workspaceSlug, payload.project_id, payload) ? await draftIssues.createIssue(workspaceSlug, payload.project_id, payload)
: await currentIssueStore.createIssue(workspaceSlug, payload.project_id, payload, viewId); : createIssue && (await createIssue(payload.project_id, payload));
if (!response) throw new Error(); if (!response) throw new Error();
currentIssueStore.fetchIssues(workspaceSlug, payload.project_id, "mutation", viewId);
if (payload.cycle_id && payload.cycle_id !== "" && storeType !== EIssuesStoreType.CYCLE) if (payload.cycle_id && payload.cycle_id !== "" && storeType !== EIssuesStoreType.CYCLE)
await addIssueToCycle(response, payload.cycle_id); await addIssueToCycle(response, payload.cycle_id);
if (payload.module_ids && payload.module_ids.length > 0 && storeType !== EIssuesStoreType.MODULE) if (payload.module_ids && payload.module_ids.length > 0 && storeType !== EIssuesStoreType.MODULE)
@ -217,7 +190,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
try { try {
isDraft isDraft
? await draftIssues.updateIssue(workspaceSlug, payload.project_id, data.id, payload) ? await draftIssues.updateIssue(workspaceSlug, payload.project_id, data.id, payload)
: await currentIssueStore.updateIssue(workspaceSlug, payload.project_id, data.id, payload, viewId); : updateIssue && (await updateIssue(payload.project_id, data.id, payload));
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
@ -234,7 +207,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: "Issue could not be created. Please try again.", message: "Issue could not be updated. Please try again.",
}); });
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_UPDATED, eventName: ISSUE_UPDATED,
@ -244,13 +217,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
} }
}; };
const handleFormSubmit = async (formData: Partial<TIssue>, is_draft_issue: boolean = false) => { const handleFormSubmit = async (payload: Partial<TIssue>, is_draft_issue: boolean = false) => {
if (!workspaceSlug || !formData.project_id || !storeType) return; if (!workspaceSlug || !payload.project_id || !storeType) return;
const payload: Partial<TIssue> = {
...formData,
description_html: formData.description_html ?? "<p></p>",
};
let response: TIssue | undefined = undefined; let response: TIssue | undefined = undefined;
if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue); if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue);

View File

@ -234,10 +234,10 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
message: () => "Cycle remove from issue failed", message: () => "Cycle remove from issue failed",
}, },
}); });
const response = await removeFromCyclePromise; await removeFromCyclePromise;
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_UPDATED, eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" }, payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
updates: { updates: {
changed_property: "cycle_id", changed_property: "cycle_id",
change_details: "", change_details: "",

View File

@ -46,24 +46,24 @@ export const ProfileActivity = observer(() => {
{userProfileActivity.results.map((activity) => ( {userProfileActivity.results.map((activity) => (
<div key={activity.id} className="flex gap-3"> <div key={activity.id} className="flex gap-3">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? ( {activity.actor_detail?.avatar && activity.actor_detail?.avatar !== "" ? (
<img <img
src={activity.actor_detail.avatar} src={activity.actor_detail?.avatar}
alt={activity.actor_detail.display_name} alt={activity.actor_detail?.display_name}
height={24} height={24}
width={24} width={24}
className="rounded" className="rounded"
/> />
) : ( ) : (
<div className="grid h-6 w-6 place-items-center rounded border-2 bg-gray-700 text-xs text-white"> <div className="grid h-6 w-6 place-items-center rounded border-2 bg-gray-700 text-xs text-white">
{activity.actor_detail.display_name?.charAt(0)} {activity.actor_detail?.display_name?.charAt(0)}
</div> </div>
)} )}
</div> </div>
<div className="-mt-1 w-4/5 break-words"> <div className="-mt-1 w-4/5 break-words">
<p className="text-sm text-custom-text-200"> <p className="text-sm text-custom-text-200">
<span className="font-medium text-custom-text-100"> <span className="font-medium text-custom-text-100">
{currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail.display_name}{" "} {currentUser?.id === activity.actor_detail?.id ? "You" : activity.actor_detail?.display_name}{" "}
</span> </span>
{activity.field ? ( {activity.field ? (
<ActivityMessage activity={activity} showIssue /> <ActivityMessage activity={activity} showIssue />

View File

@ -96,21 +96,22 @@ export const ProfileSidebar = observer(() => {
)} )}
<img <img
src={ src={
userProjectsData.user_data.cover_image ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab" userProjectsData.user_data?.cover_image ??
"https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"
} }
alt={userProjectsData.user_data.display_name} alt={userProjectsData.user_data?.display_name}
className="h-32 w-full object-cover" className="h-32 w-full object-cover"
/> />
<div className="absolute -bottom-[26px] left-5 h-[52px] w-[52px] rounded"> <div className="absolute -bottom-[26px] left-5 h-[52px] w-[52px] rounded">
{userProjectsData.user_data.avatar && userProjectsData.user_data.avatar !== "" ? ( {userProjectsData.user_data?.avatar && userProjectsData.user_data?.avatar !== "" ? (
<img <img
src={userProjectsData.user_data.avatar} src={userProjectsData.user_data?.avatar}
alt={userProjectsData.user_data.display_name} alt={userProjectsData.user_data?.display_name}
className="h-full w-full rounded object-cover" className="h-full w-full rounded object-cover"
/> />
) : ( ) : (
<div className="flex h-[52px] w-[52px] items-center justify-center rounded bg-custom-background-90 capitalize text-custom-text-100"> <div className="flex h-[52px] w-[52px] items-center justify-center rounded bg-custom-background-90 capitalize text-custom-text-100">
{userProjectsData.user_data.first_name?.[0]} {userProjectsData.user_data?.first_name?.[0]}
</div> </div>
)} )}
</div> </div>
@ -118,9 +119,9 @@ export const ProfileSidebar = observer(() => {
<div className="px-5"> <div className="px-5">
<div className="mt-[38px]"> <div className="mt-[38px]">
<h4 className="text-lg font-semibold"> <h4 className="text-lg font-semibold">
{userProjectsData.user_data.first_name} {userProjectsData.user_data.last_name} {userProjectsData.user_data?.first_name} {userProjectsData.user_data?.last_name}
</h4> </h4>
<h6 className="text-sm text-custom-text-200">({userProjectsData.user_data.display_name})</h6> <h6 className="text-sm text-custom-text-200">({userProjectsData.user_data?.display_name})</h6>
</div> </div>
<div className="mt-6 space-y-5"> <div className="mt-6 space-y-5">
{userDetails.map((detail) => ( {userDetails.map((detail) => (

View File

@ -44,7 +44,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
const handleRemove = async () => { const handleRemove = async () => {
if (!workspaceSlug || !projectId || !userDetails) return; if (!workspaceSlug || !projectId || !userDetails) return;
if (userDetails.member.id === currentUser?.id) { if (userDetails.member?.id === currentUser?.id) {
await leaveProject(workspaceSlug.toString(), projectId.toString()) await leaveProject(workspaceSlug.toString(), projectId.toString())
.then(async () => { .then(async () => {
captureEvent(PROJECT_MEMBER_LEAVE, { captureEvent(PROJECT_MEMBER_LEAVE, {
@ -62,7 +62,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
}) })
); );
} else } else
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member.id).catch( await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id).catch(
(err) => (err) =>
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
@ -84,12 +84,12 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
/> />
<div className="group flex items-center justify-between px-3 py-4 hover:bg-custom-background-90"> <div className="group flex items-center justify-between px-3 py-4 hover:bg-custom-background-90">
<div className="flex items-center gap-x-4 gap-y-2"> <div className="flex items-center gap-x-4 gap-y-2">
{userDetails.member.avatar && userDetails.member.avatar !== "" ? ( {userDetails.member?.avatar && userDetails.member?.avatar !== "" ? (
<Link href={`/${workspaceSlug}/profile/${userDetails.member.id}`}> <Link href={`/${workspaceSlug}/profile/${userDetails.member?.id}`}>
<span className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize text-white"> <span className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize text-white">
<img <img
src={userDetails.member.avatar} src={userDetails.member?.avatar}
alt={userDetails.member.display_name || userDetails.member.email} alt={userDetails.member?.display_name || userDetails.member?.email}
className="absolute left-0 top-0 h-full w-full rounded object-cover" className="absolute left-0 top-0 h-full w-full rounded object-cover"
/> />
</span> </span>
@ -97,23 +97,23 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
) : ( ) : (
<Link href={`/${workspaceSlug}/profile/${userDetails.id}`}> <Link href={`/${workspaceSlug}/profile/${userDetails.id}`}>
<span className="relative flex h-10 w-10 items-center justify-center rounded bg-gray-700 p-4 capitalize text-white"> <span className="relative flex h-10 w-10 items-center justify-center rounded bg-gray-700 p-4 capitalize text-white">
{(userDetails.member.display_name ?? userDetails.member.email ?? "?")[0]} {(userDetails.member?.display_name ?? userDetails.member?.email ?? "?")[0]}
</span> </span>
</Link> </Link>
)} )}
<div> <div>
<Link href={`/${workspaceSlug}/profile/${userDetails.member.id}`}> <Link href={`/${workspaceSlug}/profile/${userDetails.member?.id}`}>
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{userDetails.member.first_name} {userDetails.member.last_name} {userDetails.member?.first_name} {userDetails.member?.last_name}
</span> </span>
</Link> </Link>
<div className="flex items-center"> <div className="flex items-center">
<p className="text-xs text-custom-text-300">{userDetails.member.display_name}</p> <p className="text-xs text-custom-text-300">{userDetails.member?.display_name}</p>
{isAdmin && ( {isAdmin && (
<> <>
<Dot height={16} width={16} className="text-custom-text-300" /> <Dot height={16} width={16} className="text-custom-text-300" />
<p className="text-xs text-custom-text-300">{userDetails.member.email}</p> <p className="text-xs text-custom-text-300">{userDetails.member?.email}</p>
</> </>
)} )}
</div> </div>
@ -126,12 +126,12 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
<div className="item-center flex gap-1 rounded px-2 py-0.5"> <div className="item-center flex gap-1 rounded px-2 py-0.5">
<span <span
className={`flex items-center rounded text-xs font-medium ${ className={`flex items-center rounded text-xs font-medium ${
userDetails.member.id !== currentUser?.id ? "" : "text-custom-text-400" userDetails.member?.id !== currentUser?.id ? "" : "text-custom-text-400"
}`} }`}
> >
{ROLE[userDetails.role]} {ROLE[userDetails.role]}
</span> </span>
{userDetails.member.id !== currentUser?.id && ( {userDetails.member?.id !== currentUser?.id && (
<span className="grid place-items-center"> <span className="grid place-items-center">
<ChevronDown className="h-3 w-3" /> <ChevronDown className="h-3 w-3" />
</span> </span>
@ -142,7 +142,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
onChange={(value: EUserProjectRoles) => { onChange={(value: EUserProjectRoles) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
updateMember(workspaceSlug.toString(), projectId.toString(), userDetails.member.id, { updateMember(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id, {
role: value, role: value,
}).catch((err) => { }).catch((err) => {
const error = err.error; const error = err.error;
@ -156,7 +156,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
}); });
}} }}
disabled={ disabled={
userDetails.member.id === currentUser?.id || !currentProjectRole || currentProjectRole < userDetails.role userDetails.member?.id === currentUser?.id || !currentProjectRole || currentProjectRole < userDetails.role
} }
placement="bottom-end" placement="bottom-end"
> >
@ -170,8 +170,8 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
); );
})} })}
</CustomSelect> </CustomSelect>
{(isAdmin || userDetails.member.id === currentUser?.id) && ( {(isAdmin || userDetails.member?.id === currentUser?.id) && (
<Tooltip tooltipContent={userDetails.member.id === currentUser?.id ? "Leave project" : "Remove member"}> <Tooltip tooltipContent={userDetails.member?.id === currentUser?.id ? "Leave project" : "Remove member"}>
<button <button
type="button" type="button"
onClick={() => setRemoveMemberModal(true)} onClick={() => setRemoveMemberModal(true)}

View File

@ -50,9 +50,9 @@ export const MemberSelect: React.FC<Props> = observer((props) => {
value={value} value={value}
label={ label={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{selectedOption && <Avatar name={selectedOption.member.display_name} src={selectedOption.member.avatar} />} {selectedOption && <Avatar name={selectedOption.member?.display_name} src={selectedOption.member?.avatar} />}
{selectedOption ? ( {selectedOption ? (
selectedOption.member.display_name selectedOption.member?.display_name
) : ( ) : (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Ban className="h-3.5 w-3.5 rotate-90 text-custom-sidebar-text-400" /> <Ban className="h-3.5 w-3.5 rotate-90 text-custom-sidebar-text-400" />

View File

@ -11,7 +11,7 @@ type Props = {
export const ProjectLogo: React.FC<Props> = (props) => { export const ProjectLogo: React.FC<Props> = (props) => {
const { className, logo } = props; const { className, logo } = props;
if (logo.in_use === "icon" && logo.icon) if (logo && logo.in_use === "icon" && logo.icon)
return ( return (
<span <span
style={{ style={{
@ -23,7 +23,7 @@ export const ProjectLogo: React.FC<Props> = (props) => {
</span> </span>
); );
if (logo.in_use === "emoji" && logo.emoji) if (logo && logo.in_use === "emoji" && logo.emoji)
return ( return (
<span className={cn("text-base", className)}> <span className={cn("text-base", className)}>
{logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))} {logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))}

View File

@ -135,27 +135,36 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
} }
}, [fields, append]); }, [fields, append]);
const options = uninvitedPeople?.map((userId) => { const options = uninvitedPeople
const memberDetails = getWorkspaceMemberDetails(userId); ?.map((userId) => {
const memberDetails = getWorkspaceMemberDetails(userId);
return { if (!memberDetails?.member) return;
value: `${memberDetails?.member.id}`, return {
query: `${memberDetails?.member.first_name} ${ value: `${memberDetails?.member.id}`,
memberDetails?.member.last_name query: `${memberDetails?.member.first_name} ${
} ${memberDetails?.member.display_name.toLowerCase()}`, memberDetails?.member.last_name
content: ( } ${memberDetails?.member.display_name.toLowerCase()}`,
<div className="flex w-full items-center gap-2"> content: (
<div className="flex-shrink-0 pt-0.5"> <div className="flex w-full items-center gap-2">
<Avatar name={memberDetails?.member.display_name} src={memberDetails?.member.avatar} /> <div className="flex-shrink-0 pt-0.5">
<Avatar name={memberDetails?.member.display_name} src={memberDetails?.member.avatar} />
</div>
<div className="truncate">
{memberDetails?.member.display_name} (
{memberDetails?.member.first_name + " " + memberDetails?.member.last_name})
</div>
</div> </div>
<div className="truncate"> ),
{memberDetails?.member.display_name} ( };
{memberDetails?.member.first_name + " " + memberDetails?.member.last_name}) })
</div> .filter((option) => !!option) as
</div> | {
), value: string;
}; query: string;
}); content: React.JSX.Element;
}[]
| undefined;
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
@ -204,6 +213,8 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
render={({ field: { value, onChange } }) => { render={({ field: { value, onChange } }) => {
const selectedMember = getWorkspaceMemberDetails(value); const selectedMember = getWorkspaceMemberDetails(value);
if (!selectedMember?.member) return <></>;
return ( return (
<CustomSearchSelect <CustomSearchSelect
value={value} value={value}

View File

@ -73,7 +73,7 @@ export const ConfirmWorkspaceMemberRemove: React.FC<Props> = observer((props) =>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100"> <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
{currentUser?.id === userDetails.id {currentUser?.id === userDetails.id
? "Leave workspace?" ? "Leave workspace?"
: `Remove ${userDetails.display_name}?`} : `Remove ${userDetails?.display_name}?`}
</Dialog.Title> </Dialog.Title>
<div className="mt-2"> <div className="mt-2">
{currentUser?.id === userDetails.id ? ( {currentUser?.id === userDetails.id ? (
@ -84,7 +84,7 @@ export const ConfirmWorkspaceMemberRemove: React.FC<Props> = observer((props) =>
) : ( ) : (
<p className="text-sm text-custom-text-200"> <p className="text-sm text-custom-text-200">
Are you sure you want to remove member-{" "} Are you sure you want to remove member-{" "}
<span className="font-bold">{userDetails.display_name}</span>? They will no longer have <span className="font-bold">{userDetails?.display_name}</span>? They will no longer have
access to this workspace. This action cannot be undone. access to this workspace. This action cannot be undone.
</p> </p>
)} )}

View File

@ -171,3 +171,20 @@ export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
start_date: block.start_date ? new Date(block.start_date) : null, start_date: block.start_date ? new Date(block.start_date) : null,
target_date: block.target_date ? new Date(block.target_date) : null, target_date: block.target_date ? new Date(block.target_date) : null,
})); }));
export function getChangedIssuefields(
formData: Partial<TIssue>,
dirtyFields: { [key: string]: boolean | undefined }
) {
const changedFields: Partial<TIssue> = {};
const dirtyFieldKeys = Object.keys(dirtyFields) as (keyof TIssue)[];
for (const dirtyField of dirtyFieldKeys) {
if (!!dirtyFields[dirtyField]) {
changedFields[dirtyField] = formData[dirtyField];
}
}
return changedFields;
}

View File

@ -0,0 +1,576 @@
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { useApplication, useIssues } from "./store";
import {
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
IIssueFilterOptions,
TIssue,
TIssueKanbanFilters,
TLoader,
} from "@plane/types";
import { useCallback, useMemo } from "react";
interface IssueActions {
fetchIssues?: (projectId: string, loadType: TLoader) => Promise<TIssue[] | undefined>;
removeIssue: (projectId: string, issueId: string) => Promise<void>;
createIssue?: (projectId: string, data: Partial<TIssue>) => Promise<TIssue | undefined>;
updateIssue?: (projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
removeIssueFromView?: (projectId: string, issueId: string) => Promise<void>;
archiveIssue?: (projectId: string, issueId: string) => Promise<void>;
restoreIssue?: (projectId: string, issueId: string) => Promise<void>;
updateFilters: (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
}
export const useIssuesActions = (storeType: EIssuesStoreType): IssueActions => {
const projectIssueActions = useProjectIssueActions();
const cycleIssueActions = useCycleIssueActions();
const moduleIssueActions = useModuleIssueActions();
const profileIssueActions = useProfileIssueActions();
const projectViewIssueActions = useProjectViewIssueActions();
const draftIssueActions = useDraftIssueActions();
const archivedIssueActions = useArchivedIssueActions();
const globalIssueActions = useGlobalIssueActions();
switch (storeType) {
case EIssuesStoreType.PROJECT_VIEW:
return projectViewIssueActions;
case EIssuesStoreType.PROFILE:
return profileIssueActions;
case EIssuesStoreType.CYCLE:
return cycleIssueActions;
case EIssuesStoreType.MODULE:
return moduleIssueActions;
case EIssuesStoreType.ARCHIVED:
return archivedIssueActions;
case EIssuesStoreType.DRAFT:
return draftIssueActions;
case EIssuesStoreType.GLOBAL:
return globalIssueActions;
case EIssuesStoreType.PROJECT:
default:
return projectIssueActions;
}
};
const useProjectIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
const {
router: { workspaceSlug },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType);
},
[issues.fetchIssues, workspaceSlug]
);
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data);
},
[issues.createIssue, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
},
[issues.updateIssue, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId);
},
[issues.removeIssue, workspaceSlug]
);
const archiveIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId);
},
[issues.archiveIssue, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters);
},
[issuesFilter.updateFilters, workspaceSlug]
);
return useMemo(
() => ({
fetchIssues,
createIssue,
updateIssue,
removeIssue,
archiveIssue,
updateFilters,
}),
[fetchIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters]
);
};
const useCycleIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const {
router: { workspaceSlug, cycleId },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!cycleId || !workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, cycleId);
},
[issues.fetchIssues, cycleId, workspaceSlug]
);
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!cycleId || !workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, cycleId);
},
[issues.createIssue, cycleId, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!cycleId || !workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, cycleId);
},
[issues.updateIssue, cycleId, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!cycleId || !workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, cycleId);
},
[issues.removeIssue, cycleId, workspaceSlug]
);
const removeIssueFromView = useCallback(
async (projectId: string, issueId: string) => {
if (!cycleId || !workspaceSlug) return;
return await issues.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
},
[issues.removeIssueFromCycle, cycleId, workspaceSlug]
);
const archiveIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!cycleId || !workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, cycleId);
},
[issues.archiveIssue, cycleId, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!cycleId || !workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters, cycleId);
},
[issuesFilter.updateFilters, cycleId, workspaceSlug]
);
return useMemo(
() => ({
fetchIssues,
createIssue,
updateIssue,
removeIssue,
removeIssueFromView,
archiveIssue,
updateFilters,
}),
[fetchIssues, createIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, updateFilters]
);
};
const useModuleIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const {
router: { workspaceSlug, moduleId },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!moduleId || !workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, moduleId);
},
[issues.fetchIssues, moduleId, workspaceSlug]
);
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!moduleId || !workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, moduleId);
},
[issues.createIssue, moduleId, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!moduleId || !workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, moduleId);
},
[issues.updateIssue, moduleId, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!moduleId || !workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, moduleId);
},
[issues.removeIssue, moduleId, workspaceSlug]
);
const removeIssueFromView = useCallback(
async (projectId: string, issueId: string) => {
if (!moduleId || !workspaceSlug) return;
return await issues.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
},
[issues.removeIssueFromModule, moduleId, workspaceSlug]
);
const archiveIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!moduleId || !workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, moduleId);
},
[issues.archiveIssue, moduleId, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!moduleId || !workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters, moduleId);
},
[issuesFilter.updateFilters, moduleId]
);
return useMemo(
() => ({
fetchIssues,
createIssue,
updateIssue,
removeIssue,
removeIssueFromView,
archiveIssue,
updateFilters,
}),
[fetchIssues, createIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, updateFilters]
);
};
const useProfileIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROFILE);
const {
router: { workspaceSlug, userId },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!userId || !workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, userId);
},
[issues.fetchIssues, userId, workspaceSlug]
);
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!userId || !workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, userId);
},
[issues.createIssue, userId, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!userId || !workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, userId);
},
[issues.updateIssue, userId, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!userId || !workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, userId);
},
[issues.removeIssue, userId, workspaceSlug]
);
const archiveIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!userId || !workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, userId);
},
[issues.archiveIssue, userId, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!userId || !workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters, userId);
},
[issuesFilter.updateFilters, userId, workspaceSlug]
);
return useMemo(
() => ({
fetchIssues,
createIssue,
updateIssue,
removeIssue,
archiveIssue,
updateFilters,
}),
[fetchIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters]
);
};
const useProjectViewIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
const {
router: { workspaceSlug, viewId },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!viewId || !workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, viewId);
},
[issues.fetchIssues, viewId, workspaceSlug]
);
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!viewId || !workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, viewId);
},
[issues.createIssue, viewId, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!viewId || !workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, viewId);
},
[issues.updateIssue, viewId, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!viewId || !workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, viewId);
},
[issues.removeIssue, viewId, workspaceSlug]
);
const archiveIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!viewId || !workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, viewId);
},
[issues.archiveIssue, viewId, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!viewId || !workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters, viewId);
},
[issuesFilter.updateFilters, viewId, workspaceSlug]
);
return useMemo(
() => ({
fetchIssues,
createIssue,
updateIssue,
removeIssue,
archiveIssue,
updateFilters,
}),
[fetchIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters]
);
};
const useDraftIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
const {
router: { workspaceSlug },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType);
},
[issues.fetchIssues, workspaceSlug]
);
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data);
},
[issues.createIssue, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
},
[issues.updateIssue, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId);
},
[issues.removeIssue, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters);
},
[issuesFilter.updateFilters]
);
return useMemo(
() => ({
fetchIssues,
createIssue,
updateIssue,
removeIssue,
updateFilters,
}),
[fetchIssues, createIssue, updateIssue, removeIssue, updateFilters]
);
};
const useArchivedIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
const {
router: { workspaceSlug },
} = useApplication();
const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => {
if (!workspaceSlug) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType);
},
[issues.fetchIssues]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId);
},
[issues.removeIssue]
);
const restoreIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!workspaceSlug) return;
return await issues.restoreIssue(workspaceSlug, projectId, issueId);
},
[issues.restoreIssue]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters);
},
[issuesFilter.updateFilters]
);
return useMemo(
() => ({
fetchIssues,
removeIssue,
restoreIssue,
updateFilters,
}),
[fetchIssues, removeIssue, restoreIssue, updateFilters]
);
};
const useGlobalIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.GLOBAL);
const {
router: { workspaceSlug, globalViewId },
} = useApplication();
const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => {
if (!globalViewId || !workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, globalViewId);
},
[issues.createIssue, globalViewId, workspaceSlug]
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!globalViewId || !workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, globalViewId);
},
[issues.updateIssue, globalViewId, workspaceSlug]
);
const removeIssue = useCallback(
async (projectId: string, issueId: string) => {
if (!globalViewId || !workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, globalViewId);
},
[issues.removeIssue, globalViewId, workspaceSlug]
);
const updateFilters = useCallback(
async (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
if (!globalViewId || !workspaceSlug) return;
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters, globalViewId);
},
[issuesFilter.updateFilters, globalViewId, workspaceSlug]
);
return useMemo(
() => ({
createIssue,
updateIssue,
removeIssue,
updateFilters,
}),
[createIssue, updateIssue, removeIssue, updateFilters]
);
};

View File

@ -83,7 +83,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
avatar: formData.avatar, avatar: formData.avatar,
cover_image: formData.cover_image, cover_image: formData.cover_image,
role: formData.role, role: formData.role,
display_name: formData.display_name, display_name: formData?.display_name,
user_timezone: formData.user_timezone, user_timezone: formData.user_timezone,
}; };
@ -195,7 +195,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
src={watch("avatar")} src={watch("avatar")}
className="absolute left-0 top-0 h-full w-full rounded-lg object-cover" className="absolute left-0 top-0 h-full w-full rounded-lg object-cover"
onClick={() => setIsImageUploadModalOpen(true)} onClick={() => setIsImageUploadModalOpen(true)}
alt={myProfile.display_name} alt={myProfile?.display_name}
role="button" role="button"
/> />
</div> </div>
@ -377,14 +377,14 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
value={value} value={value}
onChange={onChange} onChange={onChange}
ref={ref} ref={ref}
hasError={Boolean(errors.display_name)} hasError={Boolean(errors?.display_name)}
placeholder="Enter your display name" placeholder="Enter your display name"
className={`w-full ${errors.display_name ? "border-red-500" : ""}`} className={`w-full ${errors?.display_name ? "border-red-500" : ""}`}
maxLength={24} maxLength={24}
/> />
)} )}
/> />
{errors.display_name && <span className="text-xs text-red-500">Please enter display name</span>} {errors?.display_name && <span className="text-xs text-red-500">Please enter display name</span>}
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">

View File

@ -17,7 +17,7 @@ export interface IArchivedIssues {
// computed // computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// actions // actions
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue>; fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>; restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: undefined; quickAddIssue: undefined;

View File

@ -35,7 +35,7 @@ export interface ICycleIssuesFilter {
projectId: string, projectId: string,
filterType: EIssueFilterType, filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
cycleId?: string | undefined cycleId: string
) => Promise<void>; ) => Promise<void>;
} }
@ -136,10 +136,9 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
projectId: string, projectId: string,
type: EIssueFilterType, type: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
cycleId: string | undefined = undefined cycleId: string
) => { ) => {
try { try {
if (!cycleId) throw new Error("Cycle id is required");
if (isEmpty(this.filters) || isEmpty(this.filters[cycleId]) || isEmpty(filters)) return; if (isEmpty(this.filters) || isEmpty(this.filters[cycleId]) || isEmpty(filters)) return;
const _filters = { const _filters = {

View File

@ -27,33 +27,23 @@ export interface ICycleIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader, loadType: TLoader,
cycleId?: string | undefined cycleId: string
) => Promise<TIssue[] | undefined>; ) => Promise<TIssue[] | undefined>;
createIssue: ( createIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
cycleId?: string | undefined cycleId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
updateIssue: ( updateIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
cycleId?: string | undefined cycleId: string
) => Promise<void>;
removeIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
cycleId?: string | undefined
) => Promise<void>;
archiveIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
cycleId?: string | undefined
) => Promise<void>; ) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => Promise<void>;
quickAddIssue: ( quickAddIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -67,7 +57,7 @@ export interface ICycleIssues {
issueIds: string[], issueIds: string[],
fetchAddedIssues?: boolean fetchAddedIssues?: boolean
) => Promise<void>; ) => Promise<void>;
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<TIssue>; removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
transferIssuesFromCycle: ( transferIssuesFromCycle: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -156,11 +146,9 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader = "init-loader", loadType: TLoader = "init-loader",
cycleId: string | undefined = undefined cycleId: string
) => { ) => {
try { try {
if (!cycleId) throw new Error("Cycle Id is required");
this.loader = loadType; this.loader = loadType;
const params = this.rootIssueStore?.cycleIssuesFilter?.appliedFilters; const params = this.rootIssueStore?.cycleIssuesFilter?.appliedFilters;
@ -185,15 +173,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
} }
}; };
createIssue = async ( createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, cycleId: string) => {
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
cycleId: string | undefined = undefined
) => {
try { try {
if (!cycleId) throw new Error("Cycle Id is required");
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id], false); await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id], false);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
@ -209,11 +190,9 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
cycleId: string | undefined = undefined cycleId: string
) => { ) => {
try { try {
if (!cycleId) throw new Error("Cycle Id is required");
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
} catch (error) { } catch (error) {
@ -222,15 +201,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
} }
}; };
removeIssue = async ( removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
cycleId: string | undefined = undefined
) => {
try { try {
if (!cycleId) throw new Error("Cycle Id is required");
await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
@ -244,15 +216,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
} }
}; };
archiveIssue = async ( archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
cycleId: string | undefined = undefined
) => {
try { try {
if (!cycleId) throw new Error("Cycle Id is required");
await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
@ -290,7 +255,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); if (cycleId) this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
throw error; throw error;
} }
}; };
@ -335,10 +300,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
this.rootStore.issues.updateIssue(issueId, { cycle_id: null }); this.rootStore.issues.updateIssue(issueId, { cycle_id: null });
const response = await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId); await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
return response;
} catch (error) { } catch (error) {
throw error; throw error;
} }

View File

@ -18,7 +18,7 @@ export interface IIssueStoreActions {
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>; archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<void>; addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<void>;
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<TIssue>; removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<any>; addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<any>;
removeModulesFromIssue: ( removeModulesFromIssue: (
workspaceSlug: string, workspaceSlug: string,

View File

@ -35,7 +35,7 @@ export interface IModuleIssuesFilter {
projectId: string, projectId: string,
filterType: EIssueFilterType, filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
moduleId?: string | undefined moduleId: string
) => Promise<void>; ) => Promise<void>;
} }
@ -136,10 +136,9 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
projectId: string, projectId: string,
type: EIssueFilterType, type: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
moduleId: string | undefined = undefined moduleId: string
) => { ) => {
try { try {
if (!moduleId) throw new Error("Module id is required");
if (isEmpty(this.filters) || isEmpty(this.filters[moduleId]) || isEmpty(filters)) return; if (isEmpty(this.filters) || isEmpty(this.filters[moduleId]) || isEmpty(filters)) return;
const _filters = { const _filters = {

View File

@ -25,33 +25,23 @@ export interface IModuleIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader, loadType: TLoader,
moduleId?: string | undefined moduleId: string
) => Promise<TIssue[] | undefined>; ) => Promise<TIssue[] | undefined>;
createIssue: ( createIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
moduleId?: string | undefined moduleId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
updateIssue: ( updateIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
moduleId?: string | undefined moduleId: string
) => Promise<void>;
removeIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
moduleId?: string | undefined
) => Promise<void>;
archiveIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
moduleId?: string | undefined
) => Promise<void>; ) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => Promise<void>;
quickAddIssue: ( quickAddIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -160,11 +150,9 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader = "init-loader", loadType: TLoader = "init-loader",
moduleId: string | undefined = undefined moduleId: string
) => { ) => {
try { try {
if (!moduleId) throw new Error("Module Id is required");
this.loader = loadType; this.loader = loadType;
const params = this.rootIssueStore?.moduleIssuesFilter?.appliedFilters; const params = this.rootIssueStore?.moduleIssuesFilter?.appliedFilters;
@ -190,15 +178,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
} }
}; };
createIssue = async ( createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => {
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
moduleId: string | undefined = undefined
) => {
try { try {
if (!moduleId) throw new Error("Module Id is required");
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id], false); await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id], false);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
@ -214,11 +195,9 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
moduleId: string | undefined = undefined moduleId: string
) => { ) => {
try { try {
if (!moduleId) throw new Error("Module Id is required");
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
} catch (error) { } catch (error) {
@ -227,15 +206,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
} }
}; };
removeIssue = async ( removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
moduleId: string | undefined = undefined
) => {
try { try {
if (!moduleId) throw new Error("Module Id is required");
await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
@ -249,15 +221,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
} }
}; };
archiveIssue = async ( archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
moduleId: string | undefined = undefined
) => {
try { try {
if (!moduleId) throw new Error("Module Id is required");
await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);

View File

@ -36,7 +36,7 @@ export interface IProfileIssuesFilter {
projectId: string | undefined, projectId: string | undefined,
filterType: EIssueFilterType, filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
userId?: string | undefined userId: string
) => Promise<void>; ) => Promise<void>;
} }
@ -125,11 +125,9 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
projectId: string | undefined, projectId: string | undefined,
type: EIssueFilterType, type: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
userId: string | undefined = undefined userId: string
) => { ) => {
try { try {
if (!userId) throw new Error("user id is required");
if (isEmpty(this.filters) || isEmpty(this.filters[userId]) || isEmpty(filters)) return; if (isEmpty(this.filters) || isEmpty(this.filters[userId]) || isEmpty(filters)) return;
const _filters = { const _filters = {

View File

@ -27,34 +27,24 @@ export interface IProfileIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string | undefined, projectId: string | undefined,
loadType: TLoader, loadType: TLoader,
userId?: string | undefined, userId: string,
view?: "assigned" | "created" | "subscribed" view?: "assigned" | "created" | "subscribed"
) => Promise<TIssue[]>; ) => Promise<TIssue[]>;
createIssue: ( createIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
userId?: string | undefined userId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
updateIssue: ( updateIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
userId?: string | undefined userId: string
) => Promise<void>;
removeIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
userId?: string | undefined
) => Promise<void>;
archiveIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
userId?: string | undefined
) => Promise<void>; ) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, userId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, userId: string) => Promise<void>;
quickAddIssue: undefined; quickAddIssue: undefined;
} }
@ -150,15 +140,14 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string | undefined, projectId: string | undefined,
loadType: TLoader = "init-loader", loadType: TLoader = "init-loader",
userId?: string | undefined, userId: string,
view?: "assigned" | "created" | "subscribed" view?: "assigned" | "created" | "subscribed"
) => { ) => {
try { try {
if (!userId) throw new Error("user id is required");
if (!view) throw new Error("current tab view is required");
this.loader = loadType; this.loader = loadType;
this.currentView = view; if (view) this.currentView = view;
if (!this.currentView) throw new Error("current tab view is required");
const uniqueViewId = `${workspaceSlug}_${view}`; const uniqueViewId = `${workspaceSlug}_${view}`;
@ -193,15 +182,8 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
} }
}; };
createIssue = async ( createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, userId: string) => {
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
userId: string | undefined = undefined
) => {
try { try {
if (!userId) throw new Error("user id is required");
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
const uniqueViewId = `${workspaceSlug}_${this.currentView}`; const uniqueViewId = `${workspaceSlug}_${this.currentView}`;
@ -223,11 +205,9 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
userId: string | undefined = undefined userId: string
) => { ) => {
try { try {
if (!userId) throw new Error("user id is required");
this.rootStore.issues.updateIssue(issueId, data); this.rootStore.issues.updateIssue(issueId, data);
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, data.id as keyof TIssue, data); await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, data.id as keyof TIssue, data);
} catch (error) { } catch (error) {
@ -258,13 +238,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
} }
}; };
archiveIssue = async ( archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, userId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
userId: string | undefined = undefined
) => {
if (!userId) return;
try { try {
await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId);

View File

@ -35,7 +35,7 @@ export interface IProjectViewIssuesFilter {
projectId: string, projectId: string,
filterType: EIssueFilterType, filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
viewId?: string | undefined viewId: string
) => Promise<void>; ) => Promise<void>;
} }
@ -134,11 +134,9 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
projectId: string, projectId: string,
type: EIssueFilterType, type: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
viewId: string | undefined = undefined viewId: string
) => { ) => {
try { try {
if (!viewId) throw new Error("View id is required");
if (isEmpty(this.filters) || isEmpty(this.filters[viewId]) || isEmpty(filters)) return; if (isEmpty(this.filters) || isEmpty(this.filters[viewId]) || isEmpty(filters)) return;
const _filters = { const _filters = {

View File

@ -21,33 +21,23 @@ export interface IProjectViewIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader, loadType: TLoader,
viewId?: string | undefined viewId: string
) => Promise<TIssue[] | undefined>; ) => Promise<TIssue[] | undefined>;
createIssue: ( createIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
viewId?: string | undefined viewId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
updateIssue: ( updateIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
viewId?: string | undefined viewId: string
) => Promise<void>;
removeIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
viewId?: string | undefined
) => Promise<void>;
archiveIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
viewId?: string | undefined
) => Promise<void>; ) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>;
quickAddIssue: ( quickAddIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -124,15 +114,8 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
return issues; return issues;
} }
fetchIssues = async ( fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader", viewId: string) => {
workspaceSlug: string,
projectId: string,
loadType: TLoader = "init-loader",
viewId: string | undefined = undefined
) => {
try { try {
if (!viewId) throw new Error("View Id is required");
this.loader = loadType; this.loader = loadType;
const params = this.rootIssueStore?.projectViewIssuesFilter?.appliedFilters; const params = this.rootIssueStore?.projectViewIssuesFilter?.appliedFilters;
@ -157,15 +140,8 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
} }
}; };
createIssue = async ( createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, viewId: string) => {
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
viewId: string | undefined = undefined
) => {
try { try {
if (!viewId) throw new Error("View Id is required");
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
runInAction(() => { runInAction(() => {
@ -174,7 +150,7 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
throw error; throw error;
} }
}; };
@ -184,27 +160,18 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
viewId: string | undefined = undefined viewId: string
) => { ) => {
try { try {
if (!viewId) throw new Error("View Id is required");
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
throw error; throw error;
} }
}; };
removeIssue = async ( removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
viewId: string | undefined = undefined
) => {
try { try {
if (!viewId) throw new Error("View Id is required");
await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
const issueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === issueId); const issueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === issueId);
@ -213,27 +180,20 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
this.issues[viewId].splice(issueIndex, 1); this.issues[viewId].splice(issueIndex, 1);
}); });
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
throw error; throw error;
} }
}; };
archiveIssue = async ( archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
viewId: string | undefined = undefined
) => {
try { try {
if (!viewId) throw new Error("View Id is required");
await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId);
runInAction(() => { runInAction(() => {
pull(this.issues[viewId], issueId); pull(this.issues[viewId], issueId);
}); });
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
throw error; throw error;
} }
}; };
@ -263,7 +223,7 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); if (viewId) this.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
throw error; throw error;
} }
}; };

View File

@ -37,7 +37,7 @@ export interface IWorkspaceIssuesFilter {
projectId: string | undefined, projectId: string | undefined,
filterType: EIssueFilterType, filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
viewId?: string | undefined viewId: string
) => Promise<void>; ) => Promise<void>;
//helper action //helper action
getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined; getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined;
@ -156,10 +156,9 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
projectId: string | undefined, projectId: string | undefined,
type: EIssueFilterType, type: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
viewId: string | undefined = undefined viewId: string
) => { ) => {
try { try {
if (!viewId) throw new Error("View id is required");
const issueFilters = this.getIssueFilters(viewId); const issueFilters = this.getIssueFilters(viewId);
if (!issueFilters || isEmpty(filters)) return; if (!issueFilters || isEmpty(filters)) return;

View File

@ -23,27 +23,23 @@ export interface IWorkspaceIssues {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
viewId?: string | undefined viewId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
updateIssue: ( updateIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
viewId?: string | undefined viewId: string
) => Promise<void>;
removeIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
viewId?: string | undefined
) => Promise<void>; ) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>;
archiveIssue: ( archiveIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, issueId: string,
viewId?: string | undefined viewId?: string | undefined
) => Promise<void>; ) => Promise<void>;
quickAddIssue: undefined;
} }
export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssues { export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssues {
@ -61,6 +57,8 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
issueService; issueService;
issueArchiveService; issueArchiveService;
quickAddIssue = undefined;
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore) {
super(_rootStore); super(_rootStore);
@ -139,15 +137,8 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
} }
}; };
createIssue = async ( createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, viewId: string) => {
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
viewId: string | undefined = undefined
) => {
try { try {
if (!viewId) throw new Error("View id is required");
const uniqueViewId = `${workspaceSlug}_${viewId}`; const uniqueViewId = `${workspaceSlug}_${viewId}`;
const response = await this.issueService.createIssue(workspaceSlug, projectId, data); const response = await this.issueService.createIssue(workspaceSlug, projectId, data);
@ -169,11 +160,9 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
projectId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<TIssue>, data: Partial<TIssue>,
viewId: string | undefined = undefined viewId: string
) => { ) => {
try { try {
if (!viewId) throw new Error("View id is required");
this.rootStore.issues.updateIssue(issueId, data); this.rootStore.issues.updateIssue(issueId, data);
await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
} catch (error) { } catch (error) {
@ -182,15 +171,8 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
} }
}; };
removeIssue = async ( removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
viewId: string | undefined = undefined
) => {
try { try {
if (!viewId) throw new Error("View id is required");
const uniqueViewId = `${workspaceSlug}_${viewId}`; const uniqueViewId = `${workspaceSlug}_${viewId}`;
await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);

View File

@ -128,7 +128,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
const memberDetails = this.getWorkspaceMemberDetails(userId); const memberDetails = this.getWorkspaceMemberDetails(userId);
if (!memberDetails) return false; if (!memberDetails) return false;
const memberSearchQuery = `${memberDetails.member.first_name} ${memberDetails.member.last_name} ${ const memberSearchQuery = `${memberDetails.member.first_name} ${memberDetails.member.last_name} ${
memberDetails.member.display_name memberDetails.member?.display_name
} ${memberDetails.member.email ?? ""}`; } ${memberDetails.member.email ?? ""}`;
return memberSearchQuery.toLowerCase()?.includes(searchQuery.toLowerCase()); return memberSearchQuery.toLowerCase()?.includes(searchQuery.toLowerCase());
}); });