[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
import { useMobxStore } from "lib/mobx/store-provider";
export const IssuePeekOverview: React.FC = observer(() => {
// states
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">
{users.map((user) => (
<a
key={user.display_name ?? "None"}
key={user?.display_name ?? "None"}
href={`/${workspaceSlug}/profile/${user.id}`}
target="_blank"
rel="noopener noreferrer"
@ -36,16 +36,16 @@ export const AnalyticsLeaderBoard: React.FC<Props> = ({ users, title, emptyState
<img
src={user.avatar}
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 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>
)}
<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>
</div>
<span className="flex-shrink-0">{user.count}</span>

View File

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

View File

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

View File

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

View File

@ -71,7 +71,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
<div className="-mt-1 break-words">
<p className="text-sm text-custom-text-200">
<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>
{activity.field ? (
<ActivityMessage activity={activity} showIssue />

View File

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

View File

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

View File

@ -33,11 +33,7 @@ import { ProjectLogo } from "components/project";
export const ProjectViewIssuesHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query as {
workspaceSlug: string;
projectId: string;
viewId: string;
};
const { workspaceSlug, projectId, viewId } = router.query;
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
@ -61,15 +57,21 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, viewId);
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
viewId.toString()
);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
if (!workspaceSlug || !projectId || !viewId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
@ -81,23 +83,41 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
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]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, viewId);
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
viewId.toString()
);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, viewId);
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
viewId.toString()
);
},
[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
);
const options = members?.map((member) => ({
value: member.member.display_name,
query: member.member.display_name ?? "",
const options = members
?.map((member) => {
if (!member?.member) return;
return {
value: member.member?.display_name,
query: member.member?.display_name ?? "",
content: (
<div className="flex items-center gap-2">
<Avatar name={member?.member.display_name} src={member?.member.avatar} />
{member.member.display_name}
<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 (
<div className="grid grid-cols-3 items-center gap-2 rounded-md bg-custom-background-80 px-2 py-3">

View File

@ -33,7 +33,10 @@ export const JiraImportUsers: FC = () => {
workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug?.toString() ?? "") : null
);
const options = members?.map((member) => ({
const options = members
?.map((member) => {
if (!member?.member) return;
return {
value: member.member.email,
query: member.member.display_name ?? "",
content: (
@ -42,7 +45,15 @@ export const JiraImportUsers: FC = () => {
{member.member.display_name}
</div>
),
}));
};
})
.filter((member) => !!member) as
| {
value: string;
query: string;
content: JSX.Element;
}[]
| undefined;
return (
<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>
<div className="mt-2 flex items-center gap-2 text-xs text-custom-text-200">
<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>
<CustomMenu ellipsis>

View File

@ -217,10 +217,10 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
message: () => "Cycle remove from issue failed",
},
});
const response = await removeFromCyclePromise;
await removeFromCyclePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
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 { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// components
import { TOAST_TYPE, setToast } from "@plane/ui";
import { CalendarChart } from "components/issues";
// hooks
import { useIssues, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// ui
// 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 { TGroupedIssues, TIssue } from "@plane/types";
import { TGroupedIssues } from "@plane/types";
import { EIssuesStoreType } from "constants/issue";
import { IQuickActionProps } from "../list/list-view-types";
import { EIssueActions } from "../types";
import { handleDragDrop } from "./utils";
import { useIssues, useUser } from "hooks/store";
import { EUserProjectRoles } from "constants/project";
type CalendarStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW;
interface IBaseCalendarRoot {
issueStore: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues;
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
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>;
};
storeType: CalendarStoreType;
addIssuesToView?: (issueIds: string[]) => Promise<any>;
viewId?: string;
isCompletedCycle?: boolean;
@ -36,10 +32,8 @@ interface IBaseCalendarRoot {
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const {
issueStore,
issuesFilterStore,
QuickActions,
issueActions,
storeType,
addIssuesToView,
viewId,
isCompletedCycle = false,
@ -50,16 +44,18 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { workspaceSlug, projectId } = router.query;
// hooks
const { issueMap } = useIssues();
const {
membership: { currentProjectRole },
} = useUser();
const { issues, issuesFilter, issueMap } = useIssues(storeType);
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
useIssuesActions(storeType);
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) => {
if (!result) return;
@ -76,10 +72,9 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
result.destination,
workspaceSlug?.toString(),
projectId?.toString(),
issueStore,
issueMap,
groupedIssueIds,
viewId
updateIssue
).catch((err) => {
setToast({
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 (
<>
<div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4">
<DragDropContext onDragEnd={onDragEnd}>
<CalendarChart
issuesFilterStore={issuesFilterStore}
issuesFilterStore={issuesFilter}
issues={issueMap}
groupedIssueIds={groupedIssueIds}
layout={displayFilters?.calendar?.layout}
@ -113,34 +99,21 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
<QuickActions
customActionButton={customActionButton}
issue={issue}
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.DELETE)}
handleUpdate={
issueActions[EIssueActions.UPDATE]
? async (data) => handleIssues(issue.target_date ?? "", data, EIssueActions.UPDATE)
: 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
handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
handleRemoveFromView={async () =>
removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)
}
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
readOnly={!isEditingAllowed || isCompletedCycle}
/>
)}
addIssuesToView={addIssuesToView}
quickAddCallback={issueStore.quickAddIssue}
quickAddCallback={issues.quickAddIssue}
viewId={viewId}
readOnly={!isEditingAllowed || isCompletedCycle}
updateFilters={updateFilters}
/>
</DragDropContext>
</div>

View File

@ -5,8 +5,10 @@ import { observer } from "mobx-react-lite";
import { Spinner } from "@plane/ui";
import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TGroupedIssues, TIssue, TIssueKanbanFilters, TIssueMap } from "@plane/types";
import { ICalendarWeek } from "./types";
// constants
import { EIssuesStoreType } from "constants/issue";
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store";
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 { IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssuesFilter } from "store/issue/project-views";
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
import { ICalendarWeek } from "./types";
type Props = {
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
@ -33,6 +33,11 @@ type Props = {
addIssuesToView?: (issueIds: string[]) => Promise<any>;
viewId?: string;
readOnly?: boolean;
updateFilters?: (
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
};
export const CalendarChart: React.FC<Props> = observer((props) => {
@ -46,6 +51,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
quickAddCallback,
addIssuesToView,
viewId,
updateFilters,
readOnly = false,
} = props;
// store hooks
@ -74,7 +80,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
return (
<>
<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">
<CalendarWeekHeader isLoading={!issues} showWeekends={showWeekends} />
<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 { ToggleSwitch } from "@plane/ui";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TCalendarLayouts, TIssueKanbanFilters } from "@plane/types";
// constants
import { CALENDAR_LAYOUTS } from "constants/calendar";
import { EIssueFilterType } from "constants/issue";
@ -17,18 +18,21 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssuesFilter } from "store/issue/project-views";
import { TCalendarLayouts } from "@plane/types";
interface ICalendarHeader {
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) => {
const { issuesFilterStore, viewId } = props;
const { issuesFilterStore, updateFilters } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { projectId } = router.query;
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 handleLayoutChange = (layout: TCalendarLayouts) => {
if (!workspaceSlug || !projectId) return;
if (!projectId || !updateFilters) return;
issuesFilterStore.updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
calendar: {
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
layout,
},
},
viewId
);
});
issueCalendarView.updateCalendarPayload(
layout === "month"
@ -76,20 +74,14 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
const handleToggleWeekends = () => {
const showWeekends = issuesFilterStore.issueFilters?.displayFilters?.calendar?.show_weekends ?? false;
if (!workspaceSlug || !projectId) return;
if (!projectId || !updateFilters) return;
issuesFilterStore.updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
calendar: {
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
show_weekends: !showWeekends,
},
},
viewId
);
});
};
return (

View File

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

View File

@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react";
import { useCallback } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
//hooks
@ -7,40 +7,15 @@ import { useCycle, useIssues } from "hooks/store";
import { CycleIssueQuickActions } from "components/issues";
import { BaseCalendarRoot } from "../base-calendar-root";
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants
import { EIssuesStoreType } from "constants/issue";
export const CycleCalendarLayout: React.FC = observer(() => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCompletedCycleIds } = useCycle();
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
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 || !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]
);
const { issues } = useIssues(EIssuesStoreType.CYCLE);
if (!cycleId) return null;
@ -57,13 +32,11 @@ export const CycleCalendarLayout: React.FC = observer(() => {
return (
<BaseCalendarRoot
issueStore={issues}
issuesFilterStore={issuesFilter}
QuickActions={CycleIssueQuickActions}
addIssuesToView={addIssuesToView}
issueActions={issueActions}
viewId={cycleId.toString()}
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 { useRouter } from "next/router";
// hooks
import { useIssues } from "hooks/store";
// components
import { ModuleIssueQuickActions } from "components/issues";
import { BaseCalendarRoot } from "../base-calendar-root";
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
export const ModuleCalendarLayout: React.FC = observer(() => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const { workspaceSlug, projectId, moduleId } = router.query ;
const issueActions = useMemo(
() => ({
[EIssueActions.UPDATE]: async (issue: TIssue) => {
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 {issues} = useIssues(EIssuesStoreType.MODULE)
if (!moduleId) return null;
const addIssuesToView = useCallback(
(issueIds: string[]) => {
@ -53,12 +28,10 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
return (
<BaseCalendarRoot
issueStore={issues}
issuesFilterStore={issuesFilter}
QuickActions={ModuleIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.MODULE}
addIssuesToView={addIssuesToView}
viewId={moduleId}
viewId={moduleId.toString()}
/>
);
});

View File

@ -1,48 +1,10 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
import { BaseCalendarRoot } from "../base-calendar-root";
export const CalendarLayout: React.FC = observer(() => {
const router = useRouter();
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}
/>
);
});
export const CalendarLayout: React.FC = observer(() => (
<BaseCalendarRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
));

View File

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

View File

@ -1,21 +1,16 @@
import { DraggableLocation } from "@hello-pangea/dnd";
import { ICycleIssues } from "store/issue/cycle";
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";
import { TGroupedIssues, IIssueMap, TIssue } from "@plane/types";
export const handleDragDrop = async (
source: DraggableLocation,
destination: DraggableLocation,
workspaceSlug: string | undefined,
projectId: string | undefined,
store: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues,
issueMap: IIssueMap,
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 destinationColumnId = destination?.droppableId || null;
@ -31,12 +26,11 @@ export const handleDragDrop = async (
const [removed] = sourceIssues.splice(source.index, 1);
const removedIssueDetail = issueMap[removed];
const updateIssue = {
const updatedIssue = {
id: removedIssueDetail?.id,
target_date: destinationColumnId,
};
if (viewId) return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
else return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
return await updateIssue(projectId, updatedIssue.id, updatedIssue);
}
};

View File

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

View File

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

View File

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

View File

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

View File

@ -2,47 +2,13 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store";
// components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root";
export const CycleGanttLayout: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, cycleId } = router.query;
// store hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { fetchCycleDetails } = useCycle();
const { cycleId } = router.query;
const issueActions = {
[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()}
/>
);
return <BaseGanttRoot viewId={cycleId?.toString()} storeType={EIssuesStoreType.CYCLE} />;
});

View File

@ -2,47 +2,13 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { EIssuesStoreType } from "constants/issue";
import { useIssues, useModule } from "hooks/store";
// components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root";
export const ModuleGanttLayout: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, moduleId } = router.query;
// store hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const { fetchModuleDetails } = useModule();
const { moduleId } = router.query;
const issueActions = {
[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()}
/>
);
return <BaseGanttRoot viewId={moduleId?.toString()} storeType={EIssuesStoreType.MODULE} />;
});

View File

@ -1,33 +1,8 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
import { BaseGanttRoot } from "./base-gantt-root";
export const GanttLayout: React.FC = observer(() => {
// 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} />;
});
export const GanttLayout: React.FC = observer(() =>( <BaseGanttRoot storeType={EIssuesStoreType.PROJECT} />));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ import {
TSubGroupedIssues,
TUnGroupedIssues,
} from "@plane/types";
import { EIssueActions } from "../types";
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
interface IKanbanGroup {
@ -25,7 +24,7 @@ interface IKanbanGroup {
group_by: string | null;
sub_group_id: string;
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;
enableQuickIssueCreate?: boolean;
quickAddCallback?: (
@ -53,7 +52,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
issueIds,
peekIssueId,
isDragDisabled,
handleIssues,
updateIssue,
quickActions,
canEditProperties,
enableQuickIssueCreate,
@ -135,7 +134,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
displayProperties={displayProperties}
isDragDisabled={isDragDisabled}
handleIssues={handleIssues}
updateIssue={updateIssue}
quickActions={quickActions}
canEditProperties={canEditProperties}
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 { useRouter } from "next/router";
// hooks
@ -7,8 +7,6 @@ import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store";
// ui
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// components
import { BaseKanBanRoot } from "../base-kanban-root";
@ -19,35 +17,9 @@ export const CycleKanBanLayout: React.FC = observer(() => {
const { workspaceSlug, projectId, cycleId } = router.query;
// store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { issues } = useIssues(EIssuesStoreType.CYCLE);
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 =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
@ -63,9 +35,6 @@ export const CycleKanBanLayout: React.FC = observer(() => {
return (
<BaseKanBanRoot
issueActions={issueActions}
issues={issues}
issuesFilter={issuesFilter}
showLoader
QuickActions={CycleIssueQuickActions}
viewId={cycleId?.toString() ?? ""}

View File

@ -1,49 +1,11 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
// components
import { ProjectIssueQuickActions } from "components/issues";
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";
export interface IKanBanLayout {}
export const DraftKanBanLayout: React.FC = observer(() => {
const router = useRouter();
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}
/>
);
});
export const DraftKanBanLayout: React.FC = observer(() => (
<BaseKanBanRoot showLoader QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.DRAFT} />
));

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hook
@ -7,9 +7,7 @@ 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";
export interface IModuleKanBanLayout {}
@ -19,39 +17,10 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
const { workspaceSlug, projectId, moduleId } = router.query;
// store
const { issues, issuesFilter } = 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]
);
const { issues } = useIssues(EIssuesStoreType.MODULE);
return (
<BaseKanBanRoot
issueActions={issueActions}
issues={issues}
issuesFilter={issuesFilter}
showLoader
QuickActions={ModuleIssueQuickActions}
viewId={moduleId?.toString()}

View File

@ -1,48 +1,18 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store";
import { useUser } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
// constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root";
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 {
membership: { currentWorkspaceAllProjectsRole },
} = 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]
);
} = useUser();
const canEditPropertiesBasedOnProject = (projectId: string) => {
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
@ -52,9 +22,6 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
return (
<BaseKanBanRoot
issueActions={issueActions}
issuesFilter={issuesFilter}
issues={issues}
showLoader
QuickActions={ProjectIssueQuickActions}
storeType={EIssuesStoreType.PROFILE}

View File

@ -1,54 +1,12 @@
import { useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx store
import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store/use-issues";
// components
// types
import { TIssue } from "@plane/types";
// constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root";
export interface IKanBanLayout {}
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}
/>
);
});
export const KanBanLayout: React.FC = observer(() => (
<BaseKanBanRoot showLoader QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
));

View File

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

View File

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

View File

@ -1,12 +1,5 @@
import { DraggableLocation } from "@hello-pangea/dnd";
import { ICycleIssues } from "store/issue/cycle";
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";
import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TUnGroupedIssues, TIssue } from "@plane/types";
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
const sortOrderDefaultValue = 65535;
@ -48,24 +41,16 @@ export const handleDragDrop = async (
destination: DraggableLocation | null | undefined,
workspaceSlug: string | undefined,
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,
groupBy: string | null,
issueMap: IIssueMap,
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;
let updateIssue: any = {};
let updatedIssue: any = {};
const sourceDroppableId = source?.droppableId;
const destinationDroppableId = destination?.droppableId;
@ -100,8 +85,7 @@ export const handleDragDrop = async (
const [removed] = sourceIssues.splice(source.index, 1);
if (removed) {
if (viewId) return await store?.removeIssue(workspaceSlug, projectId, removed); //, viewId);
else return await store?.removeIssue(workspaceSlug, projectId, removed);
return await removeIssue(projectId, removed);
}
} else {
//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 removedIssueDetail = issueMap[removed];
updateIssue = {
updatedIssue = {
id: removedIssueDetail?.id,
project_id: removedIssueDetail?.project_id,
};
// for both horizontal and vertical dnd
updateIssue = {
...updateIssue,
updatedIssue = {
...updatedIssue,
...handleSortOrder(
sourceDroppableId === destinationDroppableId ? sourceIssues : destinationIssues,
destination.index,
@ -136,19 +120,19 @@ export const handleDragDrop = async (
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
if (sourceGroupByColumnId != destinationGroupByColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state_id: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
if (groupBy === "state") updatedIssue = { ...updatedIssue, state_id: destinationGroupByColumnId };
if (groupBy === "priority") updatedIssue = { ...updatedIssue, priority: destinationGroupByColumnId };
}
} else {
if (subGroupBy === "state")
updateIssue = {
...updateIssue,
updatedIssue = {
...updatedIssue,
state_id: destinationSubGroupByColumnId,
priority: destinationGroupByColumnId,
};
if (subGroupBy === "priority")
updateIssue = {
...updateIssue,
updatedIssue = {
...updatedIssue,
state_id: destinationGroupByColumnId,
priority: destinationSubGroupByColumnId,
};
@ -156,15 +140,13 @@ export const handleDragDrop = async (
} else {
// for horizontal dnd
if (sourceColumnId != destinationColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state_id: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
if (groupBy === "state") updatedIssue = { ...updatedIssue, state_id: destinationGroupByColumnId };
if (groupBy === "priority") updatedIssue = { ...updatedIssue, priority: destinationGroupByColumnId };
}
}
if (updateIssue && updateIssue?.id) {
if (viewId)
return await store?.updateIssue(workspaceSlug, updateIssue.project_id, updateIssue.id, updateIssue, viewId);
else return await store?.updateIssue(workspaceSlug, updateIssue.project_id, updateIssue.id, updateIssue);
if (updatedIssue && updatedIssue?.id) {
return updateIssue && (await updateIssue(updatedIssue.project_id, updatedIssue.id, updatedIssue));
}
}
};

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import { CreateUpdateIssueModal } from "components/issues";
// ui
// mobx
// hooks
import { TCreateModalStoreTypes } from "constants/issue";
import { EIssuesStoreType } from "constants/issue";
import { useEventTracker } from "hooks/store";
// types
import { TIssue, ISearchIssueResponse } from "@plane/types";
@ -21,7 +21,7 @@ interface IHeaderGroupByCard {
count: number;
issuePayload: Partial<TIssue>;
disableIssueCreation?: boolean;
storeType: TCreateModalStoreTypes;
storeType: EIssuesStoreType;
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 { useRouter } from "next/router";
// hooks
import { ArchivedIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
// constants
import { EIssueActions } from "../../types";
import { BaseListRoot } from "../base-list-root";
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;
return (
<BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ArchivedIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROJECT}
storeType={EIssuesStoreType.ARCHIVED}
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 { useRouter } from "next/router";
// hooks
@ -7,9 +7,7 @@ import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
// constants
import { EIssueActions } from "../../types";
import { BaseListRoot } from "../base-list-root";
export interface ICycleListLayout {}
@ -18,34 +16,9 @@ export const CycleListLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
// store
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { issues } = useIssues(EIssuesStoreType.CYCLE);
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 =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
@ -61,10 +34,7 @@ export const CycleListLayout: React.FC = observer(() => {
return (
<BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={CycleIssueQuickActions}
issueActions={issueActions}
viewId={cycleId?.toString()}
storeType={EIssuesStoreType.CYCLE}
addIssuesToView={addIssuesToView}

View File

@ -1,49 +1,19 @@
import { FC, useMemo } from "react";
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants
import { BaseListRoot } from "../base-list-root";
export const DraftIssueListLayout: FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspaceSlug, projectId } = router.query;
if (!workspaceSlug || !projectId) return null;
// store
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}
/>
);
return <BaseListRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.DRAFT} />;
});

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx store
@ -7,8 +7,6 @@ import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants
import { BaseListRoot } from "../base-list-root";
@ -18,40 +16,11 @@ export const ModuleListLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
const { issues, issuesFilter } = 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]
);
const { issues } = useIssues(EIssuesStoreType.MODULE);
return (
<BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ModuleIssueQuickActions}
issueActions={issueActions}
viewId={moduleId?.toString()}
storeType={EIssuesStoreType.MODULE}
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 { useRouter } from "next/router";
// hooks
import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project";
import { useIssues, useUser } from "hooks/store";
import { useUser } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants
import { BaseListRoot } from "../base-list-root";
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 {
membership: { currentWorkspaceAllProjectsRole },
} = 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 currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
@ -53,10 +23,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
return (
<BaseListRoot
issuesFilter={issuesFilter}
issues={issues}
QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROFILE}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/>

View File

@ -1,55 +1,19 @@
import { FC, useMemo } from "react";
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// hooks
import { ProjectIssueQuickActions } from "components/issues";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../../types";
// constants
import { BaseListRoot } from "../base-list-root";
export const ListLayout: FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspaceSlug, projectId } = router.query;
if (!workspaceSlug || !projectId) return null;
// store
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}
/>
);
return <BaseListRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />;
});

View File

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

View File

@ -30,7 +30,7 @@ import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-
export interface IIssueProperties {
issue: TIssue;
handleIssues: (issue: TIssue) => Promise<void>;
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
displayProperties: IIssueDisplayProperties | undefined;
isReadOnly: boolean;
className: string;
@ -38,7 +38,7 @@ export interface IIssueProperties {
}
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
const { labelMap } = useLabel();
const { captureIssueEvent } = useEventTracker();
@ -80,7 +80,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
);
const handleState = (stateId: string) => {
handleIssues({ ...issue, state_id: stateId }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { state_id: stateId }).then(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },
@ -94,7 +95,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
};
const handlePriority = (value: TIssuePriorities) => {
handleIssues({ ...issue, priority: value }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { priority: value }).then(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },
@ -108,7 +110,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
};
const handleLabel = (ids: string[]) => {
handleIssues({ ...issue, label_ids: ids }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { label_ids: ids }).then(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },
@ -122,7 +125,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
};
const handleAssignee = (ids: string[]) => {
handleIssues({ ...issue, assignee_ids: ids }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { assignee_ids: ids }).then(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },
@ -175,7 +179,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
);
const handleStartDate = (date: Date | null) => {
handleIssues({ ...issue, start_date: date ? renderFormattedPayloadDate(date) : null }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { start_date: date ? renderFormattedPayloadDate(date) : null }).then(
() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },
@ -185,11 +191,14 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
change_details: date ? renderFormattedPayloadDate(date) : null,
},
});
});
}
);
};
const handleTargetDate = (date: Date | null) => {
handleIssues({ ...issue, target_date: date ? renderFormattedPayloadDate(date) : null }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { target_date: date ? renderFormattedPayloadDate(date) : null }).then(
() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },
@ -199,11 +208,13 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
change_details: date ? renderFormattedPayloadDate(date) : null,
},
});
});
}
);
};
const handleEstimate = (value: number | null) => {
handleIssues({ ...issue, estimate_point: value }).then(() => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issue, state: "SUCCESS", element: currentLayout },

View File

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

View File

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

View File

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

View File

@ -100,7 +100,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
}}
data={issueToEdit ?? duplicateIssuePayload}
onSubmit={async (data) => {
if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data });
if (issueToEdit && handleUpdate) await handleUpdate(data);
}}
storeType={EIssuesStoreType.PROJECT}
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 { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
@ -6,6 +6,7 @@ import useSWR from "swr";
// hooks
import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties";
import { useApplication, useEventTracker, useGlobalView, useIssues, useProject, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions";
// components
import { GlobalViewsAppliedFiltersRoot, IssuePeekOverview } from "components/issues";
import { SpreadsheetView } from "components/issues/issue-layouts";
@ -14,7 +15,6 @@ import { EmptyState } from "components/empty-state";
import { SpreadsheetLayoutLoader } from "components/ui";
// types
import { TIssue, IIssueDisplayFilterOptions } from "@plane/types";
import { EIssueActions } from "../types";
// constants
import { EUserProjectRoles } from "constants/project";
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 {
issuesFilter: { filters, fetchFilters, updateFilters },
issues: { loader, groupedIssueIds, fetchIssues, updateIssue, removeIssue, archiveIssue },
issues: { loader, groupedIssueIds, fetchIssues },
} = useIssues(EIssuesStoreType.GLOBAL);
const { updateIssue, removeIssue, archiveIssue } = useIssuesActions(EIssuesStoreType.GLOBAL);
const { dataViewId, issueIds } = groupedIssueIds;
const {
@ -111,41 +112,6 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
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(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !globalViewId) return;
@ -166,14 +132,14 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
<AllIssueQuickActions
customActionButton={customActionButton}
issue={issue}
handleUpdate={async () => handleIssues({ ...issue }, EIssueActions.UPDATE)}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
handleArchive={async () => handleIssues(issue, EIssueActions.ARCHIVE)}
handleDelete={async () => removeIssue(issue.project_id, issue.id)}
handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
portalElement={portalElement}
readOnly={!canEditProperties(issue.project_id)}
/>
),
[canEditProperties, handleIssues]
[canEditProperties, removeIssue, updateIssue, archiveIssue]
);
if (loader === "init-loader" || !globalViewId || globalViewId !== dataViewId || !issueIds) {
@ -213,7 +179,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issueIds={issueIds}
quickActions={renderQuickActions}
handleIssues={handleIssues}
updateIssue={updateIssue}
canEditProperties={canEditProperties}
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 { useRouter } from "next/router";
import useSWR from "swr";
@ -19,8 +19,6 @@ import { ActiveLoader } from "components/ui";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
export const ProjectViewLayoutRoot: React.FC = observer(() => {
// router
@ -45,22 +43,6 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
{ 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;
if (!workspaceSlug || !projectId || !viewId) return <></>;
@ -81,15 +63,15 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
<Fragment>
<div className="relative h-full w-full overflow-auto">
{activeLayout === "list" ? (
<ProjectViewListLayout issueActions={issueActions} />
<ProjectViewListLayout />
) : activeLayout === "kanban" ? (
<ProjectViewKanBanLayout issueActions={issueActions} />
<ProjectViewKanBanLayout />
) : activeLayout === "calendar" ? (
<ProjectViewCalendarLayout issueActions={issueActions} />
<ProjectViewCalendarLayout />
) : activeLayout === "gantt_chart" ? (
<ProjectViewGanttLayout issueActions={issueActions} />
<ProjectViewGanttLayout />
) : activeLayout === "spreadsheet" ? (
<ProjectViewSpreadsheetLayout issueActions={issueActions} />
<ProjectViewSpreadsheetLayout />
) : null}
</div>

View File

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

View File

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

View File

@ -18,7 +18,6 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
import { IIssueDisplayProperties, TIssue } from "@plane/types";
// local components
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { EIssueActions } from "../types";
import { IssueColumn } from "./issue-column";
interface Props {
@ -30,7 +29,7 @@ interface Props {
portalElement?: HTMLDivElement | null
) => React.ReactNode;
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>;
nestingLevel: number;
issueId: string;
@ -46,7 +45,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
isEstimateEnabled,
nestingLevel,
portalElement,
handleIssues,
updateIssue,
quickActions,
canEditProperties,
isScrolled,
@ -76,7 +75,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
canEditProperties={canEditProperties}
nestingLevel={nestingLevel}
isEstimateEnabled={isEstimateEnabled}
handleIssues={handleIssues}
updateIssue={updateIssue}
portalElement={portalElement}
isScrolled={isScrolled}
isExpanded={isExpanded}
@ -96,7 +95,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
canEditProperties={canEditProperties}
nestingLevel={nestingLevel + 1}
isEstimateEnabled={isEstimateEnabled}
handleIssues={handleIssues}
updateIssue={updateIssue}
portalElement={portalElement}
isScrolled={isScrolled}
containerRef={containerRef}
@ -116,7 +115,7 @@ interface IssueRowDetailsProps {
portalElement?: HTMLDivElement | null
) => React.ReactNode;
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>;
nestingLevel: number;
issueId: string;
@ -132,7 +131,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
isEstimateEnabled,
nestingLevel,
portalElement,
handleIssues,
updateIssue,
quickActions,
canEditProperties,
isScrolled,
@ -261,7 +260,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
issueDetail={issueDetail}
disableUserActions={disableUserActions}
property={property}
handleIssues={handleIssues}
updateIssue={updateIssue}
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 { useRouter } from "next/router";
// mobx store
import { EIssuesStoreType } from "constants/issue";
import { useCycle, useIssues } from "hooks/store";
import { useCycle } from "hooks/store";
// components
import { TIssue } from "@plane/types";
import { CycleIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const CycleSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { cycleId } = router.query;
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 =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
const canEditIssueProperties = useCallback(() => !isCompletedCycle, [isCompletedCycle]);
if (!cycleId) return null;
return (
<BaseSpreadsheetRoot
issueStore={issues}
issueFiltersStore={issuesFilter}
viewId={cycleId}
issueActions={issueActions}
viewId={cycleId?.toString()}
QuickActions={CycleIssueQuickActions}
canEditPropertiesBasedOnProject={canEditIssueProperties}
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 { useRouter } from "next/router";
// mobx store
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
// components
import { TIssue } from "@plane/types";
import { ModuleIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const ModuleSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string };
const { moduleId } = router.query;
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
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]
);
if (!moduleId) return null;
return (
<BaseSpreadsheetRoot
issueStore={issues}
issueFiltersStore={issuesFilter}
viewId={moduleId}
issueActions={issueActions}
viewId={moduleId.toString()}
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 { useRouter } from "next/router";
// mobx store
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store";
import { TIssue } from "@plane/types";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { EIssueActions } from "../../types";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const ProjectSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter();
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}
/>
);
});
export const ProjectSpreadsheetLayout: React.FC = observer(() => (
<BaseSpreadsheetRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
));

View File

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

View File

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

View File

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

View File

@ -29,6 +29,7 @@ import { FileService } from "services/file.service";
// components
// ui
// helpers
import { getChangedIssuefields } from "helpers/issue.helper";
// types
import type { TIssue, ISearchIssueResponse } from "@plane/types";
@ -126,7 +127,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
} = useIssueDetail();
// form info
const {
formState: { errors, isDirty, isSubmitting },
formState: { errors, isDirty, isSubmitting, dirtyFields },
handleSubmit,
reset,
watch,
@ -166,7 +167,15 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const issueName = watch("name");
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);
@ -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 { ISSUE_CREATED, ISSUE_UPDATED } from "constants/event-tracker";
import { EIssuesStoreType, TCreateModalStoreTypes } from "constants/issue";
import { EIssuesStoreType } from "constants/issue";
import {
useApplication,
useEventTracker,
@ -17,6 +17,7 @@ import {
useIssueDetail,
} from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
import { useIssuesActions } from "hooks/use-issues-actions";
// components
import type { TIssue } from "@plane/types";
import { DraftIssueLayout } from "./draft-issue-layout";
@ -31,7 +32,7 @@ export interface IssuesModalProps {
onClose: () => void;
onSubmit?: (res: TIssue) => Promise<void>;
withDraftIssueWrapper?: boolean;
storeType?: TCreateModalStoreTypes;
storeType?: EIssuesStoreType;
isDraft?: boolean;
}
@ -53,41 +54,15 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
// store hooks
const { captureIssueEvent } = useEventTracker();
const {
router: { workspaceSlug, projectId, cycleId, moduleId, viewId: projectViewId },
router: { workspaceSlug, projectId, cycleId, moduleId },
} = useApplication();
const { workspaceProjectIds } = useProject();
const { fetchCycleDetails } = useCycle();
const { fetchModuleDetails } = useModule();
const { issues: projectIssues } = useIssues(EIssuesStoreType.PROJECT);
const { issues: moduleIssues } = useIssues(EIssuesStoreType.MODULE);
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 { 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
const router = useRouter();
// local storage
@ -95,7 +70,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
Record<string, Partial<TIssue>>
>("draftedIssue", {});
// current store details
const { store: currentIssueStore, viewId } = issueStores[storeType];
const { createIssue, updateIssue } = useIssuesActions(storeType);
const fetchIssueDetail = async (issueId: string | undefined) => {
if (!workspaceSlug) return;
@ -176,11 +151,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
try {
const response = is_draft_issue
? 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();
currentIssueStore.fetchIssues(workspaceSlug, payload.project_id, "mutation", viewId);
if (payload.cycle_id && payload.cycle_id !== "" && storeType !== EIssuesStoreType.CYCLE)
await addIssueToCycle(response, payload.cycle_id);
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 {
isDraft
? 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({
type: TOAST_TYPE.SUCCESS,
@ -234,7 +207,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be created. Please try again.",
message: "Issue could not be updated. Please try again.",
});
captureIssueEvent({
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) => {
if (!workspaceSlug || !formData.project_id || !storeType) return;
const payload: Partial<TIssue> = {
...formData,
description_html: formData.description_html ?? "<p></p>",
};
const handleFormSubmit = async (payload: Partial<TIssue>, is_draft_issue: boolean = false) => {
if (!workspaceSlug || !payload.project_id || !storeType) return;
let response: TIssue | undefined = undefined;
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",
},
});
const response = await removeFromCyclePromise;
await removeFromCyclePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
updates: {
changed_property: "cycle_id",
change_details: "",

View File

@ -46,24 +46,24 @@ export const ProfileActivity = observer(() => {
{userProfileActivity.results.map((activity) => (
<div key={activity.id} className="flex gap-3">
<div className="flex-shrink-0">
{activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
{activity.actor_detail?.avatar && activity.actor_detail?.avatar !== "" ? (
<img
src={activity.actor_detail.avatar}
alt={activity.actor_detail.display_name}
src={activity.actor_detail?.avatar}
alt={activity.actor_detail?.display_name}
height={24}
width={24}
className="rounded"
/>
) : (
<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 className="-mt-1 w-4/5 break-words">
<p className="text-sm text-custom-text-200">
<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>
{activity.field ? (
<ActivityMessage activity={activity} showIssue />

View File

@ -96,21 +96,22 @@ export const ProfileSidebar = observer(() => {
)}
<img
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"
/>
<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
src={userProjectsData.user_data.avatar}
alt={userProjectsData.user_data.display_name}
src={userProjectsData.user_data?.avatar}
alt={userProjectsData.user_data?.display_name}
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">
{userProjectsData.user_data.first_name?.[0]}
{userProjectsData.user_data?.first_name?.[0]}
</div>
)}
</div>
@ -118,9 +119,9 @@ export const ProfileSidebar = observer(() => {
<div className="px-5">
<div className="mt-[38px]">
<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>
<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 className="mt-6 space-y-5">
{userDetails.map((detail) => (

View File

@ -44,7 +44,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
const handleRemove = async () => {
if (!workspaceSlug || !projectId || !userDetails) return;
if (userDetails.member.id === currentUser?.id) {
if (userDetails.member?.id === currentUser?.id) {
await leaveProject(workspaceSlug.toString(), projectId.toString())
.then(async () => {
captureEvent(PROJECT_MEMBER_LEAVE, {
@ -62,7 +62,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
})
);
} else
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member.id).catch(
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id).catch(
(err) =>
setToast({
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="flex items-center gap-x-4 gap-y-2">
{userDetails.member.avatar && userDetails.member.avatar !== "" ? (
<Link href={`/${workspaceSlug}/profile/${userDetails.member.id}`}>
{userDetails.member?.avatar && userDetails.member?.avatar !== "" ? (
<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">
<img
src={userDetails.member.avatar}
alt={userDetails.member.display_name || userDetails.member.email}
src={userDetails.member?.avatar}
alt={userDetails.member?.display_name || userDetails.member?.email}
className="absolute left-0 top-0 h-full w-full rounded object-cover"
/>
</span>
@ -97,23 +97,23 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
) : (
<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">
{(userDetails.member.display_name ?? userDetails.member.email ?? "?")[0]}
{(userDetails.member?.display_name ?? userDetails.member?.email ?? "?")[0]}
</span>
</Link>
)}
<div>
<Link href={`/${workspaceSlug}/profile/${userDetails.member.id}`}>
<Link href={`/${workspaceSlug}/profile/${userDetails.member?.id}`}>
<span className="text-sm font-medium">
{userDetails.member.first_name} {userDetails.member.last_name}
{userDetails.member?.first_name} {userDetails.member?.last_name}
</span>
</Link>
<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 && (
<>
<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>
@ -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">
<span
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]}
</span>
{userDetails.member.id !== currentUser?.id && (
{userDetails.member?.id !== currentUser?.id && (
<span className="grid place-items-center">
<ChevronDown className="h-3 w-3" />
</span>
@ -142,7 +142,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
onChange={(value: EUserProjectRoles) => {
if (!workspaceSlug || !projectId) return;
updateMember(workspaceSlug.toString(), projectId.toString(), userDetails.member.id, {
updateMember(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id, {
role: value,
}).catch((err) => {
const error = err.error;
@ -156,7 +156,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
});
}}
disabled={
userDetails.member.id === currentUser?.id || !currentProjectRole || currentProjectRole < userDetails.role
userDetails.member?.id === currentUser?.id || !currentProjectRole || currentProjectRole < userDetails.role
}
placement="bottom-end"
>
@ -170,8 +170,8 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
);
})}
</CustomSelect>
{(isAdmin || userDetails.member.id === currentUser?.id) && (
<Tooltip tooltipContent={userDetails.member.id === currentUser?.id ? "Leave project" : "Remove member"}>
{(isAdmin || userDetails.member?.id === currentUser?.id) && (
<Tooltip tooltipContent={userDetails.member?.id === currentUser?.id ? "Leave project" : "Remove member"}>
<button
type="button"
onClick={() => setRemoveMemberModal(true)}

View File

@ -50,9 +50,9 @@ export const MemberSelect: React.FC<Props> = observer((props) => {
value={value}
label={
<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.member.display_name
selectedOption.member?.display_name
) : (
<div className="flex items-center gap-2">
<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) => {
const { className, logo } = props;
if (logo.in_use === "icon" && logo.icon)
if (logo && logo.in_use === "icon" && logo.icon)
return (
<span
style={{
@ -23,7 +23,7 @@ export const ProjectLogo: React.FC<Props> = (props) => {
</span>
);
if (logo.in_use === "emoji" && logo.emoji)
if (logo && logo.in_use === "emoji" && logo.emoji)
return (
<span className={cn("text-base", className)}>
{logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))}

View File

@ -135,9 +135,11 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
}
}, [fields, append]);
const options = uninvitedPeople?.map((userId) => {
const options = uninvitedPeople
?.map((userId) => {
const memberDetails = getWorkspaceMemberDetails(userId);
if (!memberDetails?.member) return;
return {
value: `${memberDetails?.member.id}`,
query: `${memberDetails?.member.first_name} ${
@ -155,7 +157,14 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
</div>
),
};
});
})
.filter((option) => !!option) as
| {
value: string;
query: string;
content: React.JSX.Element;
}[]
| undefined;
return (
<Transition.Root show={isOpen} as={React.Fragment}>
@ -204,6 +213,8 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
render={({ field: { value, onChange } }) => {
const selectedMember = getWorkspaceMemberDetails(value);
if (!selectedMember?.member) return <></>;
return (
<CustomSearchSelect
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">
{currentUser?.id === userDetails.id
? "Leave workspace?"
: `Remove ${userDetails.display_name}?`}
: `Remove ${userDetails?.display_name}?`}
</Dialog.Title>
<div className="mt-2">
{currentUser?.id === userDetails.id ? (
@ -84,7 +84,7 @@ export const ConfirmWorkspaceMemberRemove: React.FC<Props> = observer((props) =>
) : (
<p className="text-sm text-custom-text-200">
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.
</p>
)}

View File

@ -171,3 +171,20 @@ export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
start_date: block.start_date ? new Date(block.start_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,
cover_image: formData.cover_image,
role: formData.role,
display_name: formData.display_name,
display_name: formData?.display_name,
user_timezone: formData.user_timezone,
};
@ -195,7 +195,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
src={watch("avatar")}
className="absolute left-0 top-0 h-full w-full rounded-lg object-cover"
onClick={() => setIsImageUploadModalOpen(true)}
alt={myProfile.display_name}
alt={myProfile?.display_name}
role="button"
/>
</div>
@ -377,14 +377,14 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.display_name)}
hasError={Boolean(errors?.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}
/>
)}
/>
{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 className="flex flex-col gap-1">

View File

@ -17,7 +17,7 @@ export interface IArchivedIssues {
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// 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>;
restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: undefined;

View File

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

View File

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

View File

@ -18,7 +18,7 @@ export interface IIssueStoreActions {
removeIssue: (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>;
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>;
removeModulesFromIssue: (
workspaceSlug: string,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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