diff --git a/space/components/issues/peek-overview/layout.tsx b/space/components/issues/peek-overview/layout.tsx index 7345b4b28..602277f3e 100644 --- a/space/components/issues/peek-overview/layout.tsx +++ b/space/components/issues/peek-overview/layout.tsx @@ -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); diff --git a/web/components/analytics/scope-and-demand/leaderboard.tsx b/web/components/analytics/scope-and-demand/leaderboard.tsx index 9cd38dde4..ae7447b0f 100644 --- a/web/components/analytics/scope-and-demand/leaderboard.tsx +++ b/web/components/analytics/scope-and-demand/leaderboard.tsx @@ -24,7 +24,7 @@ export const AnalyticsLeaderBoard: React.FC = ({ users, title, emptyState
{users.map((user) => ( = ({ users, title, emptyState {user.display_name
) : (
- {user.display_name !== "" ? user?.display_name?.[0] : "?"} + {user?.display_name !== "" ? user?.display_name?.[0] : "?"}
)} - {user.display_name !== "" ? `${user.display_name}` : "No assignee"} + {user?.display_name !== "" ? `${user?.display_name}` : "No assignee"} {user.count} diff --git a/web/components/core/sidebar/sidebar-progress-stats.tsx b/web/components/core/sidebar/sidebar-progress-stats.tsx index 157fd2c79..6ff3d3f1e 100644 --- a/web/components/core/sidebar/sidebar-progress-stats.tsx +++ b/web/components/core/sidebar/sidebar-progress-stats.tsx @@ -137,8 +137,8 @@ export const SidebarProgressStats: React.FC = ({ key={assignee.assignee_id} title={
- - {assignee.display_name} + + {assignee?.display_name ?? ""}
} completed={assignee.completed_issues} diff --git a/web/components/cycles/active-cycle-stats.tsx b/web/components/cycles/active-cycle-stats.tsx index 7d935c347..0cf7449ae 100644 --- a/web/components/cycles/active-cycle-stats.tsx +++ b/web/components/cycles/active-cycle-stats.tsx @@ -82,7 +82,7 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => {
- {assignee.display_name} + {assignee?.display_name ?? ""}
} completed={assignee.completed_issues} diff --git a/web/components/cycles/cycle-mobile-header.tsx b/web/components/cycles/cycle-mobile-header.tsx index 942b5832b..add78943c 100644 --- a/web/components/cycles/cycle-mobile-header.tsx +++ b/web/components/cycles/cycle-mobile-header.tsx @@ -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) => { - 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) => { - 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] ); diff --git a/web/components/dashboard/widgets/recent-activity.tsx b/web/components/dashboard/widgets/recent-activity.tsx index 56056bce0..6e5c61355 100644 --- a/web/components/dashboard/widgets/recent-activity.tsx +++ b/web/components/dashboard/widgets/recent-activity.tsx @@ -71,7 +71,7 @@ export const RecentActivityWidget: React.FC = observer((props) => {

- {currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail.display_name}{" "} + {currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail?.display_name}{" "} {activity.field ? ( diff --git a/web/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx b/web/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx index cfe7dd5ca..1e796eea2 100644 --- a/web/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx +++ b/web/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx @@ -100,14 +100,14 @@ export const CollaboratorsList: React.FC = (props) => { updateIsLoading?.(false); updateTotalPages(widgetStats.total_pages); - updateResultsCount(widgetStats.results.length); + updateResultsCount(widgetStats.results?.length); }, [updateIsLoading, updateResultsCount, updateTotalPages, widgetStats]); - if (!widgetStats) return ; + if (!widgetStats || !widgetStats?.results) return ; return ( <> - {widgetStats?.results.map((user) => ( + {widgetStats?.results?.map((user) => ( { 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) => { - 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) => { - 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 diff --git a/web/components/headers/project-view-issues.tsx b/web/components/headers/project-view-issues.tsx index 4abc3edf9..ab3959716 100644 --- a/web/components/headers/project-view-issues.tsx +++ b/web/components/headers/project-view-issues.tsx @@ -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) => { - 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) => { - 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] ); diff --git a/web/components/integration/github/single-user-select.tsx b/web/components/integration/github/single-user-select.tsx index 24bd677d0..2a323e72e 100644 --- a/web/components/integration/github/single-user-select.tsx +++ b/web/components/integration/github/single-user-select.tsx @@ -44,16 +44,27 @@ export const SingleUserSelect: React.FC = ({ collaborator, index, users, workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug.toString()) : null ); - const options = members?.map((member) => ({ - value: member.member.display_name, - query: member.member.display_name ?? "", - content: ( -

- - {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: ( +
+ + {member.member?.display_name} +
+ ), + }; + }) + .filter((member) => !!member) as + | { + value: string; + query: string; + content: JSX.Element; + }[] + | undefined; return (
diff --git a/web/components/integration/jira/import-users.tsx b/web/components/integration/jira/import-users.tsx index 584ba9fee..f5d1221ae 100644 --- a/web/components/integration/jira/import-users.tsx +++ b/web/components/integration/jira/import-users.tsx @@ -33,16 +33,27 @@ export const JiraImportUsers: FC = () => { workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug?.toString() ?? "") : null ); - const options = members?.map((member) => ({ - value: member.member.email, - query: member.member.display_name ?? "", - content: ( -
- - {member.member.display_name} -
- ), - })); + const options = members + ?.map((member) => { + if (!member?.member) return; + return { + value: member.member.email, + query: member.member.display_name ?? "", + content: ( +
+ + {member.member.display_name} +
+ ), + }; + }) + .filter((member) => !!member) as + | { + value: string; + query: string; + content: JSX.Element; + }[] + | undefined; return (
diff --git a/web/components/integration/single-import.tsx b/web/components/integration/single-import.tsx index 6d1d925e9..5d83a92c9 100644 --- a/web/components/integration/single-import.tsx +++ b/web/components/integration/single-import.tsx @@ -40,7 +40,7 @@ export const SingleImport: React.FC = ({ service, refreshing, handleDelet
{renderFormattedDate(service.created_at)}| - Imported by {service.initiated_by_detail.display_name} + Imported by {service.initiated_by_detail?.display_name}
diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index f714a5279..5e56170a8 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -217,10 +217,10 @@ export const IssueDetailRoot: FC = 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: "", diff --git a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx index ab47a7399..a36b8cc47 100644 --- a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx +++ b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx @@ -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; - issueActions: { - [EIssueActions.DELETE]: (issue: TIssue) => Promise; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - [EIssueActions.RESTORE]?: (issue: TIssue) => Promise; - }; + storeType: CalendarStoreType; addIssuesToView?: (issueIds: string[]) => Promise; 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 ( <>
{ 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} />
diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx index 308393267..823866d98 100644 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -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; viewId?: string; readOnly?: boolean; + updateFilters?: ( + projectId: string, + filterType: EIssueFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters + ) => Promise; }; export const CalendarChart: React.FC = observer((props) => { @@ -46,6 +51,7 @@ export const CalendarChart: React.FC = observer((props) => { quickAddCallback, addIssuesToView, viewId, + updateFilters, readOnly = false, } = props; // store hooks @@ -74,7 +80,7 @@ export const CalendarChart: React.FC = observer((props) => { return ( <>
- +
diff --git a/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx b/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx index 6d00253da..d483ebe91 100644 --- a/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx +++ b/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx @@ -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; } export const CalendarOptionsDropdown: React.FC = 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 = 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, - { - calendar: { - ...issuesFilterStore.issueFilters?.displayFilters?.calendar, - layout, - }, + 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 = 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, - { - calendar: { - ...issuesFilterStore.issueFilters?.displayFilters?.calendar, - show_weekends: !showWeekends, - }, + updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, { + calendar: { + ...issuesFilterStore.issueFilters?.displayFilters?.calendar, + show_weekends: !showWeekends, }, - viewId - ); + }); }; return ( diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx index 6129e451b..aa055534d 100644 --- a/web/components/issues/issue-layouts/calendar/header.tsx +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -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; } export const CalendarHeader: React.FC = observer((props) => { - const { issuesFilterStore, viewId } = props; + const { issuesFilterStore, updateFilters } = props; const issueCalendarView = useCalendarView(); @@ -101,7 +112,7 @@ export const CalendarHeader: React.FC = observer((props) => { > Today - +
); diff --git a/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx b/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx index 80a21838d..128c84ba5 100644 --- a/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx @@ -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 ( ); }); diff --git a/web/components/issues/issue-layouts/calendar/roots/module-root.tsx b/web/components/issues/issue-layouts/calendar/roots/module-root.tsx index b2e2769ee..f6080630f 100644 --- a/web/components/issues/issue-layouts/calendar/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/calendar/roots/module-root.tsx @@ -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 ( ); }); diff --git a/web/components/issues/issue-layouts/calendar/roots/project-root.tsx b/web/components/issues/issue-layouts/calendar/roots/project-root.tsx index f8933a227..ad0cffe33 100644 --- a/web/components/issues/issue-layouts/calendar/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/calendar/roots/project-root.tsx @@ -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 ( - - ); -}); +export const CalendarLayout: React.FC = observer(() => ( + +)); diff --git a/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx b/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx index b50efd6c7..ff1b654e5 100644 --- a/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/calendar/roots/project-view-root.tsx @@ -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; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - }; -} - -export const ProjectViewCalendarLayout: React.FC = 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 ( ); }); diff --git a/web/components/issues/issue-layouts/calendar/utils.ts b/web/components/issues/issue-layouts/calendar/utils.ts index 82d9ce0ce..fd96ff647 100644 --- a/web/components/issues/issue-layouts/calendar/utils.ts +++ b/web/components/issues/issue-layouts/calendar/utils.ts @@ -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) => Promise ) => { - 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); } }; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx index 57e28240b..6a741b73d 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx @@ -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 (
@@ -84,7 +86,11 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => { states={projectStates} /> - +
); }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx index d2c9ba7ed..b49ddf4d6 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx @@ -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 (
@@ -83,7 +85,11 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => { states={projectStates} /> - +
); }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 278e19d65..760d2e7e4 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -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); diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 1231a31c5..11f52db80 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -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; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - }; + storeType: GanttStoreType; } export const BaseGanttRoot: React.FC = 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 = 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 = 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) => } sidebarToRender={(props) => } @@ -75,7 +74,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan enableAddBlock={isAllowed} quickAdd={ enableIssueCreation && isAllowed ? ( - + ) : undefined } showAllBlocks diff --git a/web/components/issues/issue-layouts/gantt/cycle-root.tsx b/web/components/issues/issue-layouts/gantt/cycle-root.tsx index 4d255b64f..923845e7b 100644 --- a/web/components/issues/issue-layouts/gantt/cycle-root.tsx +++ b/web/components/issues/issue-layouts/gantt/cycle-root.tsx @@ -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 ( - - ); + return ; }); diff --git a/web/components/issues/issue-layouts/gantt/module-root.tsx b/web/components/issues/issue-layouts/gantt/module-root.tsx index 3311b6c6a..e14f1339a 100644 --- a/web/components/issues/issue-layouts/gantt/module-root.tsx +++ b/web/components/issues/issue-layouts/gantt/module-root.tsx @@ -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 ( - - ); + return ; }); diff --git a/web/components/issues/issue-layouts/gantt/project-root.tsx b/web/components/issues/issue-layouts/gantt/project-root.tsx index 1f9e560d3..90fcca145 100644 --- a/web/components/issues/issue-layouts/gantt/project-root.tsx +++ b/web/components/issues/issue-layouts/gantt/project-root.tsx @@ -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 ; -}); +export const GanttLayout: React.FC = observer(() =>( )); diff --git a/web/components/issues/issue-layouts/gantt/project-view-root.tsx b/web/components/issues/issue-layouts/gantt/project-view-root.tsx index cda2a1e53..80d5e047b 100644 --- a/web/components/issues/issue-layouts/gantt/project-view-root.tsx +++ b/web/components/issues/issue-layouts/gantt/project-view-root.tsx @@ -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; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - }; -} - -export const ProjectViewGanttLayout: React.FC = 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 ( - - ); + return ; }); diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index f4f204436..0a492b5f7 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -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; - issueActions: { - [EIssueActions.DELETE]: (issue: TIssue) => Promise; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - [EIssueActions.RESTORE]?: (issue: TIssue) => Promise; - }; showLoader?: boolean; viewId?: string; - storeType?: TCreateModalStoreTypes; + storeType: KanbanStoreType; addIssuesToView?: (issueIds: string[]) => Promise; canEditPropertiesBasedOnProject?: (projectId: string) => boolean; isCompletedCycle?: boolean; @@ -58,10 +44,7 @@ type KanbanDragState = { export const BaseKanBanRoot: React.FC = observer((props: IBaseKanBanLayout) => { const { - issues, - issuesFilter, QuickActions, - issueActions, showLoader, viewId, storeType, @@ -77,7 +60,9 @@ export const BaseKanBanRoot: React.FC = 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 = 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 = 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) => ( 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 = 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 = 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} diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 55675dd39..dabecc491 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -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) => Promise) | undefined; quickActions: (issue: TIssue) => React.ReactNode; canEditProperties: (projectId: string | undefined) => boolean; scrollableContainerRef?: MutableRefObject; @@ -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) => Promise) | undefined; quickActions: (issue: TIssue) => React.ReactNode; isReadOnly: boolean; } const KanbanIssueDetailsBlock: React.FC = 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 = 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 = observer((prop issue={issue} displayProperties={displayProperties} activeLayout="Kanban" - handleIssues={updateIssue} + updateIssue={updateIssue} isReadOnly={isReadOnly} /> @@ -111,7 +106,7 @@ export const KanbanIssueBlock: React.FC = memo((props) => { isDragDisabled, draggableId, index, - handleIssues, + updateIssue, quickActions, canEditProperties, scrollableContainerRef, @@ -159,7 +154,7 @@ export const KanbanIssueBlock: React.FC = memo((props) => { diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx index ff1c92873..7a58a4933 100644 --- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx +++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx @@ -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) => Promise) | undefined; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; canEditProperties: (projectId: string | undefined) => boolean; scrollableContainerRef?: MutableRefObject; @@ -29,7 +28,7 @@ const KanbanIssueBlocksListMemo: React.FC = (props) => { issueIds, displayProperties, isDragDisabled, - handleIssues, + updateIssue, quickActions, canEditProperties, scrollableContainerRef, @@ -54,7 +53,7 @@ const KanbanIssueBlocksListMemo: React.FC = (props) => { issueId={issueId} issuesMap={issuesMap} displayProperties={displayProperties} - handleIssues={handleIssues} + updateIssue={updateIssue} quickActions={quickActions} draggableId={draggableId} index={index} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index ece578058..394f5ef18 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -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) => Promise) | undefined; quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; kanbanFilters: TIssueKanbanFilters; handleKanbanFilters: any; @@ -53,7 +52,7 @@ export interface IGroupByKanBan { ) => Promise; viewId?: string; disableIssueCreation?: boolean; - storeType?: TCreateModalStoreTypes; + storeType: KanbanStoreType; addIssuesToView?: (issueIds: string[]) => Promise; canEditProperties: (projectId: string | undefined) => boolean; scrollableContainerRef?: MutableRefObject; @@ -70,7 +69,7 @@ const GroupByKanBan: React.FC = observer((props) => { group_by, sub_group_id = "null", isDragDisabled, - handleIssues, + updateIssue, quickActions, kanbanFilters, handleKanbanFilters, @@ -164,7 +163,7 @@ const GroupByKanBan: React.FC = 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) => Promise) | 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; viewId?: string; disableIssueCreation?: boolean; - storeType?: TCreateModalStoreTypes; + storeType: KanbanStoreType; addIssuesToView?: (issueIds: string[]) => Promise; canEditProperties: (projectId: string | undefined) => boolean; scrollableContainerRef?: MutableRefObject; @@ -219,7 +218,7 @@ export const KanBan: React.FC = 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 = 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} diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index b3cc24f28..a14dd5ddc 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -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; disableIssueCreation?: boolean; - storeType?: TCreateModalStoreTypes; + storeType: KanbanStoreType; addIssuesToView?: (issueIds: string[]) => Promise; } diff --git a/web/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/components/issues/issue-layouts/kanban/kanban-group.tsx index 9d7053216..48e92feba 100644 --- a/web/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/web/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -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) => Promise) | 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} diff --git a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx index c73b12afb..19ac8a1d9 100644 --- a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx @@ -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 ( { - 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 ( - - ); -}); +export const DraftKanBanLayout: React.FC = observer(() => ( + +)); diff --git a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx index 96cfaceda..eaf96a994 100644 --- a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx @@ -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 ( { - 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] - ); + membership: { currentWorkspaceAllProjectsRole }, +} = useUser(); const canEditPropertiesBasedOnProject = (projectId: string) => { const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId]; @@ -52,9 +22,6 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => { return ( { - 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 ( - - ); -}); +export const KanBanLayout: React.FC = observer(() => ( + +)); diff --git a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx index 77689e563..c1a07c317 100644 --- a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx @@ -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; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - }; -} - -export const ProjectViewKanBanLayout: React.FC = 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 ( void; + storeType: KanbanStoreType; } const getSubGroupHeaderIssuesCount = (issueIds: TSubGroupedIssues, groupById: string) => { @@ -43,6 +43,7 @@ const SubGroupSwimlaneHeader: React.FC = ({ issueIds, sub_group_by, group_by, + storeType, list, kanbanFilters, handleKanbanFilters, @@ -62,6 +63,7 @@ const SubGroupSwimlaneHeader: React.FC = ({ kanbanFilters={kanbanFilters} handleKanbanFilters={handleKanbanFilters} issuePayload={_list.payload} + storeType={storeType} />
))} @@ -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) => Promise) | 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; @@ -99,7 +101,8 @@ const SubGroupSwimlane: React.FC = observer((props) => { sub_group_by, group_by, list, - handleIssues, + storeType, + updateIssue, quickActions, displayProperties, kanbanFilters, @@ -153,7 +156,7 @@ const SubGroupSwimlane: React.FC = 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 = observer((props) => { viewId={viewId} scrollableContainerRef={scrollableContainerRef} isDragStarted={isDragStarted} + storeType={storeType} />
)} @@ -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) => Promise) | 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; enableQuickIssueCreate: boolean; quickAddCallback?: ( @@ -208,7 +212,8 @@ export const KanBanSwimLanes: React.FC = observer((props) => { displayProperties, sub_group_by, group_by, - handleIssues, + updateIssue, + storeType, quickActions, kanbanFilters, handleKanbanFilters, @@ -261,6 +266,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { kanbanFilters={kanbanFilters} handleKanbanFilters={handleKanbanFilters} list={groupByList} + storeType={storeType} />
@@ -272,7 +278,7 @@ export const KanBanSwimLanes: React.FC = 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 = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} scrollableContainerRef={scrollableContainerRef} + storeType={storeType} /> )} diff --git a/web/components/issues/issue-layouts/kanban/utils.ts b/web/components/issues/issue-layouts/kanban/utils.ts index 617598524..855f096e6 100644 --- a/web/components/issues/issue-layouts/kanban/utils.ts +++ b/web/components/issues/issue-layouts/kanban/utils.ts @@ -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) => Promise) | undefined, + removeIssue: (projectId: string, issueId: string) => Promise | 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)); } } }; diff --git a/web/components/issues/issue-layouts/list/base-list-root.tsx b/web/components/issues/issue-layouts/list/base-list-root.tsx index 8a3d87e40..5777f4e70 100644 --- a/web/components/issues/issue-layouts/list/base-list-root.tsx +++ b/web/components/issues/issue-layouts/list/base-list-root.tsx @@ -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; - issueActions: { - [EIssueActions.DELETE]: (issue: TIssue) => Promise; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - [EIssueActions.RESTORE]?: (issue: TIssue) => Promise; - }; viewId?: string; - storeType: TCreateModalStoreTypes; + storeType: ListStoreType; addIssuesToView?: (issueIds: string[]) => Promise; 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) => ( 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} diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index a2148634c..099137348 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -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; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; quickActions: (issue: TIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; canEditProperties: (projectId: string | undefined) => boolean; } export const IssueBlock: React.FC = 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 = 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 = observer((props: IssueBlock className="relative flex items-center gap-2 whitespace-nowrap" issue={issue} isReadOnly={!canEditIssueProperties} - handleIssues={updateIssue} + updateIssue={updateIssue} displayProperties={displayProperties} activeLayout="List" /> diff --git a/web/components/issues/issue-layouts/list/blocks-list.tsx b/web/components/issues/issue-layouts/list/blocks-list.tsx index 23c364b67..2296e7b68 100644 --- a/web/components/issues/issue-layouts/list/blocks-list.tsx +++ b/web/components/issues/issue-layouts/list/blocks-list.tsx @@ -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; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; quickActions: (issue: TIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; containerRef: MutableRefObject; } export const IssueBlocksList: FC = (props) => { - const { issueIds, issuesMap, handleIssues, quickActions, displayProperties, canEditProperties, containerRef } = props; + const { issueIds, issuesMap, updateIssue, quickActions, displayProperties, canEditProperties, containerRef } = props; return (
@@ -35,7 +34,7 @@ export const IssueBlocksList: FC = (props) => { Promise; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; quickActions: (issue: TIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; enableIssueQuickAdd: boolean; @@ -36,7 +35,7 @@ export interface IGroupByList { viewId?: string ) => Promise; disableIssueCreation?: boolean; - storeType: TCreateModalStoreTypes; + storeType: EIssuesStoreType; addIssuesToView?: (issueIds: string[]) => Promise; viewId?: string; isCompletedCycle?: boolean; @@ -47,7 +46,7 @@ const GroupByList: React.FC = (props) => { issueIds, issuesMap, group_by, - handleIssues, + updateIssue, quickActions, displayProperties, enableIssueQuickAdd, @@ -142,7 +141,7 @@ const GroupByList: React.FC = (props) => { Promise; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; quickActions: (issue: TIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; showEmptyGroup: boolean; @@ -184,7 +183,7 @@ export interface IList { ) => Promise; viewId?: string; disableIssueCreation?: boolean; - storeType: TCreateModalStoreTypes; + storeType: EIssuesStoreType; addIssuesToView?: (issueIds: string[]) => Promise; isCompletedCycle?: boolean; } @@ -194,7 +193,7 @@ export const List: React.FC = (props) => { issueIds, issuesMap, group_by, - handleIssues, + updateIssue, quickActions, quickAddCallback, viewId, @@ -214,7 +213,7 @@ export const List: React.FC = (props) => { issueIds={issueIds as TUnGroupedIssues} issuesMap={issuesMap} group_by={group_by} - handleIssues={handleIssues} + updateIssue={updateIssue} quickActions={quickActions} displayProperties={displayProperties} enableIssueQuickAdd={enableIssueQuickAdd} diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index acf26adb5..fa1a393c4 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -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; disableIssueCreation?: boolean; - storeType: TCreateModalStoreTypes; + storeType: EIssuesStoreType; addIssuesToView?: (issueIds: string[]) => Promise; } diff --git a/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx b/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx index 2f3807beb..73f8e3d3b 100644 --- a/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx @@ -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 ( ); diff --git a/web/components/issues/issue-layouts/list/roots/cycle-root.tsx b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx index 46ee7f32e..26afdf25b 100644 --- a/web/components/issues/issue-layouts/list/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx @@ -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 ( { 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 ( - - ); + return ; }); diff --git a/web/components/issues/issue-layouts/list/roots/module-root.tsx b/web/components/issues/issue-layouts/list/roots/module-root.tsx index aca528a6a..3c6a8894a 100644 --- a/web/components/issues/issue-layouts/list/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/module-root.tsx @@ -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 ( { diff --git a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx index dc0c68cd8..f24683d95 100644 --- a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx @@ -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 ( diff --git a/web/components/issues/issue-layouts/list/roots/project-root.tsx b/web/components/issues/issue-layouts/list/roots/project-root.tsx index 8a0935979..fbbd26ffb 100644 --- a/web/components/issues/issue-layouts/list/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/project-root.tsx @@ -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 ( - - ); + return ; }); diff --git a/web/components/issues/issue-layouts/list/roots/project-view-root.tsx b/web/components/issues/issue-layouts/list/roots/project-view-root.tsx index 82ca03d42..260dd54bd 100644 --- a/web/components/issues/issue-layouts/list/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/project-view-root.tsx @@ -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; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - }; -} - -export const ProjectViewListLayout: React.FC = 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 = observer((props) return ( diff --git a/web/components/issues/issue-layouts/properties/all-properties.tsx b/web/components/issues/issue-layouts/properties/all-properties.tsx index c3a6bc037..8c1e33b8c 100644 --- a/web/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/components/issues/issue-layouts/properties/all-properties.tsx @@ -30,7 +30,7 @@ import { WithDisplayPropertiesHOC } from "../properties/with-display-properties- export interface IIssueProperties { issue: TIssue; - handleIssues: (issue: TIssue) => Promise; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; displayProperties: IIssueDisplayProperties | undefined; isReadOnly: boolean; className: string; @@ -38,7 +38,7 @@ export interface IIssueProperties { } export const IssueProperties: React.FC = 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,59 +80,63 @@ export const IssueProperties: React.FC = observer((props) => { ); const handleState = (stateId: string) => { - handleIssues({ ...issue, state_id: stateId }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "state", - change_details: stateId, - }, + updateIssue && + updateIssue(issue.project_id, issue.id, { state_id: stateId }).then(() => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "state", + change_details: stateId, + }, + }); }); - }); }; const handlePriority = (value: TIssuePriorities) => { - handleIssues({ ...issue, priority: value }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "priority", - change_details: value, - }, + updateIssue && + updateIssue(issue.project_id, issue.id, { priority: value }).then(() => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "priority", + change_details: value, + }, + }); }); - }); }; const handleLabel = (ids: string[]) => { - handleIssues({ ...issue, label_ids: ids }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "labels", - change_details: ids, - }, + updateIssue && + updateIssue(issue.project_id, issue.id, { label_ids: ids }).then(() => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "labels", + change_details: ids, + }, + }); }); - }); }; const handleAssignee = (ids: string[]) => { - handleIssues({ ...issue, assignee_ids: ids }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "assignees", - change_details: ids, - }, + updateIssue && + updateIssue(issue.project_id, issue.id, { assignee_ids: ids }).then(() => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "assignees", + change_details: ids, + }, + }); }); - }); }; const handleModule = useCallback( @@ -175,45 +179,52 @@ export const IssueProperties: React.FC = observer((props) => { ); const handleStartDate = (date: Date | null) => { - handleIssues({ ...issue, start_date: date ? renderFormattedPayloadDate(date) : null }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "start_date", - change_details: date ? renderFormattedPayloadDate(date) : null, - }, - }); - }); + updateIssue && + updateIssue(issue.project_id, issue.id, { start_date: date ? renderFormattedPayloadDate(date) : null }).then( + () => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "start_date", + change_details: date ? renderFormattedPayloadDate(date) : null, + }, + }); + } + ); }; const handleTargetDate = (date: Date | null) => { - handleIssues({ ...issue, target_date: date ? renderFormattedPayloadDate(date) : null }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "target_date", - change_details: date ? renderFormattedPayloadDate(date) : null, - }, - }); - }); + updateIssue && + updateIssue(issue.project_id, issue.id, { target_date: date ? renderFormattedPayloadDate(date) : null }).then( + () => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "target_date", + change_details: date ? renderFormattedPayloadDate(date) : null, + }, + }); + } + ); }; const handleEstimate = (value: number | null) => { - handleIssues({ ...issue, estimate_point: value }).then(() => { - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...issue, state: "SUCCESS", element: currentLayout }, - path: router.asPath, - updates: { - changed_property: "estimate_point", - change_details: value, - }, + updateIssue && + updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => { + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...issue, state: "SUCCESS", element: currentLayout }, + path: router.asPath, + updates: { + changed_property: "estimate_point", + change_details: value, + }, + }); }); - }); }; const redirectToIssueDetail = () => { diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx index 5f7609a03..f6c63191f 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -90,7 +90,7 @@ export const AllIssueQuickActions: React.FC = observer((props }} data={issueToEdit ?? duplicateIssuePayload} onSubmit={async (data) => { - if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); + if (issueToEdit && handleUpdate) await handleUpdate(data); }} storeType={EIssuesStoreType.PROJECT} /> diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 89beda00c..fe713ed23 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -101,7 +101,7 @@ export const CycleIssueQuickActions: React.FC = observer((pro }} data={issueToEdit ?? duplicateIssuePayload} onSubmit={async (data) => { - if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); + if (issueToEdit && handleUpdate) await handleUpdate(data); }} storeType={EIssuesStoreType.CYCLE} /> diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 26eb6997c..f24f6869e 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -100,7 +100,7 @@ export const ModuleIssueQuickActions: React.FC = observer((pr }} data={issueToEdit ?? duplicateIssuePayload} onSubmit={async (data) => { - if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); + if (issueToEdit && handleUpdate) await handleUpdate(data); }} storeType={EIssuesStoreType.MODULE} /> diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index 33b73f88c..24a2433d5 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -100,7 +100,7 @@ export const ProjectIssueQuickActions: React.FC = 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} diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index 1367eccc4..bcba7152e 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -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) => { if (!workspaceSlug || !globalViewId) return; @@ -166,14 +132,14 @@ export const AllIssueLayoutRoot: React.FC = observer(() => { 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} /> diff --git a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx index dbd6c5f96..d15e65865 100644 --- a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx @@ -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(() => {
{activeLayout === "list" ? ( - + ) : activeLayout === "kanban" ? ( - + ) : activeLayout === "calendar" ? ( - + ) : activeLayout === "gantt_chart" ? ( - + ) : activeLayout === "spreadsheet" ? ( - + ) : null}
diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx index 5a522a527..fa89b77ed 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -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; - 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; - }; + 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) => { - if (!workspaceSlug || !projectId) return; + if ( !projectId) return; - issueFiltersStore.updateFilters( - workspaceSlug, - projectId, - EIssueFilterType.DISPLAY_FILTERS, - { - ...updatedDisplayFilter, - }, - viewId - ); + updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, { + ...updatedDisplayFilter, + }); }, - [issueFiltersStore, projectId, workspaceSlug, viewId] + [ projectId, updateFilters] ); const renderQuickActions = useCallback( @@ -98,37 +71,28 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { 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 ( Promise; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | 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(null); @@ -44,7 +43,8 @@ export const IssueColumn = observer((props: Props) => { , updates: any) => - handleIssues({ ...issue, ...data }, EIssueActions.UPDATE).then(() => { + updateIssue && + updateIssue(issue.project_id, issue.id, data).then(() => { captureIssueEvent({ eventName: "Issue updated", payload: { diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx index 161aa07ae..8a8ce29f4 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -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; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; portalElement: React.MutableRefObject; 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; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; portalElement: React.MutableRefObject; 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} /> ))} diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx index 7a8977b22..b8b4fd08a 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx @@ -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 ( ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx index c52b40527..a95919cdc 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx @@ -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 ( ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx index cc570fd81..dc9d354a6 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx @@ -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 ( - - ); -}); +export const ProjectSpreadsheetLayout: React.FC = observer(() => ( + +)); diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx index dd134e070..754d87c2f 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx @@ -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; - [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; - [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; - [EIssueActions.ARCHIVE]?: (issue: TIssue) => Promise; - }; -} - -export const ProjectViewSpreadsheetLayout: React.FC = 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 ( ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx index 4bb2cbeab..896d5a4dd 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx @@ -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; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; canEditProperties: (projectId: string | undefined) => boolean; portalElement: React.MutableRefObject; containerRef: MutableRefObject; @@ -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} diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index f71634ab8..ed243d312 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -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; + updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; openIssuesListModal?: (() => void) | null; quickAddCallback?: ( workspaceSlug: string, @@ -41,7 +40,7 @@ export const SpreadsheetView: React.FC = observer((props) => { handleDisplayFilterUpdate, issueIds, quickActions, - handleIssues, + updateIssue, quickAddCallback, viewId, canEditProperties, @@ -75,7 +74,7 @@ export const SpreadsheetView: React.FC = observer((props) => { isEstimateEnabled={isEstimateEnabled} portalElement={portalRef} quickActions={quickActions} - handleIssues={handleIssues} + updateIssue={updateIssue} canEditProperties={canEditProperties} containerRef={containerRef} /> diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 03a9ae5b0..67d3c904d 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -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 = 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 = observer((props) => { const issueName = watch("name"); const handleFormSubmit = async (formData: Partial, 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 ?? "

", + }; + await onSubmit(submitData, is_draft_issue); setGptAssistantModal(false); @@ -761,3 +770,4 @@ export const IssueFormRoot: FC = observer((props) => { ); }); + diff --git a/web/components/issues/issue-modal/modal.tsx b/web/components/issues/issue-modal/modal.tsx index fa6c4fbb3..b4cf05fc8 100644 --- a/web/components/issues/issue-modal/modal.tsx +++ b/web/components/issues/issue-modal/modal.tsx @@ -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; withDraftIssueWrapper?: boolean; - storeType?: TCreateModalStoreTypes; + storeType?: EIssuesStoreType; isDraft?: boolean; } @@ -53,41 +54,15 @@ export const CreateUpdateIssueModal: React.FC = 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 = observer((prop Record> >("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 = 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 = 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 = 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 = observer((prop } }; - const handleFormSubmit = async (formData: Partial, is_draft_issue: boolean = false) => { - if (!workspaceSlug || !formData.project_id || !storeType) return; - - const payload: Partial = { - ...formData, - description_html: formData.description_html ?? "

", - }; + const handleFormSubmit = async (payload: Partial, 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); diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index 3eae8d3e8..37cd8f375 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -234,10 +234,10 @@ export const IssuePeekOverview: FC = 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: "", diff --git a/web/components/profile/overview/activity.tsx b/web/components/profile/overview/activity.tsx index 4a6cf98be..c8af1ccf8 100644 --- a/web/components/profile/overview/activity.tsx +++ b/web/components/profile/overview/activity.tsx @@ -46,24 +46,24 @@ export const ProfileActivity = observer(() => { {userProfileActivity.results.map((activity) => (
- {activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? ( + {activity.actor_detail?.avatar && activity.actor_detail?.avatar !== "" ? ( {activity.actor_detail.display_name} ) : (
- {activity.actor_detail.display_name?.charAt(0)} + {activity.actor_detail?.display_name?.charAt(0)}
)}

- {currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail.display_name}{" "} + {currentUser?.id === activity.actor_detail?.id ? "You" : activity.actor_detail?.display_name}{" "} {activity.field ? ( diff --git a/web/components/profile/sidebar.tsx b/web/components/profile/sidebar.tsx index 3121f5b2e..adb9c63d7 100644 --- a/web/components/profile/sidebar.tsx +++ b/web/components/profile/sidebar.tsx @@ -96,21 +96,22 @@ export const ProfileSidebar = observer(() => { )} {userProjectsData.user_data.display_name}

- {userProjectsData.user_data.avatar && userProjectsData.user_data.avatar !== "" ? ( + {userProjectsData.user_data?.avatar && userProjectsData.user_data?.avatar !== "" ? ( {userProjectsData.user_data.display_name} ) : (
- {userProjectsData.user_data.first_name?.[0]} + {userProjectsData.user_data?.first_name?.[0]}
)}
@@ -118,9 +119,9 @@ export const ProfileSidebar = observer(() => {

- {userProjectsData.user_data.first_name} {userProjectsData.user_data.last_name} + {userProjectsData.user_data?.first_name} {userProjectsData.user_data?.last_name}

-
({userProjectsData.user_data.display_name})
+
({userProjectsData.user_data?.display_name})
{userDetails.map((detail) => ( diff --git a/web/components/project/member-list-item.tsx b/web/components/project/member-list-item.tsx index 43c2ce2a8..55b1b3c9a 100644 --- a/web/components/project/member-list-item.tsx +++ b/web/components/project/member-list-item.tsx @@ -44,7 +44,7 @@ export const ProjectMemberListItem: React.FC = 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 = 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 = observer((props) => { />
- {userDetails.member.avatar && userDetails.member.avatar !== "" ? ( - + {userDetails.member?.avatar && userDetails.member?.avatar !== "" ? ( + {userDetails.member.display_name @@ -97,23 +97,23 @@ export const ProjectMemberListItem: React.FC = observer((props) => { ) : ( - {(userDetails.member.display_name ?? userDetails.member.email ?? "?")[0]} + {(userDetails.member?.display_name ?? userDetails.member?.email ?? "?")[0]} )}
- + - {userDetails.member.first_name} {userDetails.member.last_name} + {userDetails.member?.first_name} {userDetails.member?.last_name}
-

{userDetails.member.display_name}

+

{userDetails.member?.display_name}

{isAdmin && ( <> -

{userDetails.member.email}

+

{userDetails.member?.email}

)}
@@ -126,12 +126,12 @@ export const ProjectMemberListItem: React.FC = observer((props) => {
{ROLE[userDetails.role]} - {userDetails.member.id !== currentUser?.id && ( + {userDetails.member?.id !== currentUser?.id && ( @@ -142,7 +142,7 @@ export const ProjectMemberListItem: React.FC = 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 = 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 = observer((props) => { ); })} - {(isAdmin || userDetails.member.id === currentUser?.id) && ( - + {(isAdmin || userDetails.member?.id === currentUser?.id) && ( +