refactor: spreadsheet layout

This commit is contained in:
Aaryan Khandelwal 2023-10-13 13:19:15 +05:30
parent 8aebf0bbd2
commit 0c13d05e27
12 changed files with 95 additions and 199 deletions

View File

@ -22,7 +22,6 @@ type Props = {
properties: Properties; properties: Properties;
handleEditIssue: (issue: IIssue) => void; handleEditIssue: (issue: IIssue) => void;
handleDeleteIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void;
setCurrentProjectId: React.Dispatch<React.SetStateAction<string | null>>;
disableUserActions: boolean; disableUserActions: boolean;
nestingLevel: number; nestingLevel: number;
}; };
@ -35,7 +34,6 @@ export const IssueColumn: React.FC<Props> = ({
properties, properties,
handleEditIssue, handleEditIssue,
handleDeleteIssue, handleDeleteIssue,
setCurrentProjectId,
disableUserActions, disableUserActions,
nestingLevel, nestingLevel,
}) => { }) => {
@ -49,7 +47,7 @@ export const IssueColumn: React.FC<Props> = ({
const openPeekOverview = () => { const openPeekOverview = () => {
const { query } = router; const { query } = router;
setCurrentProjectId(issue.project_detail.id);
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssue: issue.id }, query: { ...query, peekIssue: issue.id },

View File

@ -14,7 +14,6 @@ type Props = {
setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>; setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>;
properties: Properties; properties: Properties;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
setCurrentProjectId: React.Dispatch<React.SetStateAction<string | null>>;
disableUserActions: boolean; disableUserActions: boolean;
nestingLevel?: number; nestingLevel?: number;
}; };
@ -26,7 +25,6 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
setExpandedIssues, setExpandedIssues,
properties, properties,
handleIssueAction, handleIssueAction,
setCurrentProjectId,
disableUserActions, disableUserActions,
nestingLevel = 0, nestingLevel = 0,
}) => { }) => {
@ -57,7 +55,6 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
properties={properties} properties={properties}
handleEditIssue={() => handleIssueAction(issue, "edit")} handleEditIssue={() => handleIssueAction(issue, "edit")}
handleDeleteIssue={() => handleIssueAction(issue, "delete")} handleDeleteIssue={() => handleIssueAction(issue, "delete")}
setCurrentProjectId={setCurrentProjectId}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
nestingLevel={nestingLevel} nestingLevel={nestingLevel}
/> />
@ -75,7 +72,6 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
setExpandedIssues={setExpandedIssues} setExpandedIssues={setExpandedIssues}
properties={properties} properties={properties}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
setCurrentProjectId={setCurrentProjectId}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
nestingLevel={nestingLevel + 1} nestingLevel={nestingLevel + 1}
/> />

View File

@ -19,7 +19,7 @@ import { StateSelect } from "components/states";
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
import { renderLongDetailDateFormat } from "helpers/date-time.helper"; import { renderLongDetailDateFormat } from "helpers/date-time.helper";
// types // types
import { ICurrentUserResponse, IIssue, IState, ISubIssueResponse, Properties, TIssuePriorities, UserAuth } from "types"; import { IUser, IIssue, IState, ISubIssueResponse, Properties, TIssuePriorities, UserAuth } from "types";
// constant // constant
import { import {
CYCLE_DETAILS, CYCLE_DETAILS,
@ -42,7 +42,7 @@ type Props = {
handleDeleteIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void;
gridTemplateColumns: string; gridTemplateColumns: string;
disableUserActions: boolean; disableUserActions: boolean;
user: ICurrentUserResponse | undefined; user: IUser | undefined;
userAuth: UserAuth; userAuth: UserAuth;
nestingLevel: number; nestingLevel: number;
}; };
@ -159,6 +159,8 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
}; };
const handleStateChange = (data: string, states: IState[] | undefined) => { const handleStateChange = (data: string, states: IState[] | undefined) => {
if (!user) return;
const oldState = states?.find((s) => s.id === issue.state); const oldState = states?.find((s) => s.id === issue.state);
const newState = states?.find((s) => s.id === data); const newState = states?.find((s) => s.id === data);
@ -197,6 +199,8 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
}; };
const handlePriorityChange = (data: TIssuePriorities) => { const handlePriorityChange = (data: TIssuePriorities) => {
if (!user) return;
partialUpdateIssue({ priority: data }, issue); partialUpdateIssue({ priority: data }, issue);
trackEventServices.trackIssuePartialPropertyUpdateEvent( trackEventServices.trackIssuePartialPropertyUpdateEvent(
{ {
@ -213,6 +217,8 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
}; };
const handleAssigneeChange = (data: any) => { const handleAssigneeChange = (data: any) => {
if (!user) return;
const newData = issue.assignees ?? []; const newData = issue.assignees ?? [];
if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);

View File

@ -6,7 +6,7 @@ import { observer } from "mobx-react-lite";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// components // components
import { import {
ListInlineCreateIssueForm, // ListInlineCreateIssueForm,
SpreadsheetAssigneeColumn, SpreadsheetAssigneeColumn,
SpreadsheetCreatedOnColumn, SpreadsheetCreatedOnColumn,
SpreadsheetDueDateColumn, SpreadsheetDueDateColumn,
@ -19,7 +19,6 @@ import {
SpreadsheetUpdatedOnColumn, SpreadsheetUpdatedOnColumn,
} from "components/core"; } from "components/core";
import { CustomMenu, Icon } from "components/ui"; import { CustomMenu, Icon } from "components/ui";
import { IssuePeekOverview } from "components/issues";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
// types // types
import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueOrderByOptions } from "types"; import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueOrderByOptions } from "types";
@ -32,7 +31,7 @@ type Props = {
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void; handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
issues: IIssue[] | undefined; issues: IIssue[] | undefined;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
handleUpdateIssue: (issueId: string, data: Partial<IIssue>) => void; handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
openIssuesListModal?: (() => void) | null; openIssuesListModal?: (() => void) | null;
disableUserActions: boolean; disableUserActions: boolean;
}; };
@ -50,7 +49,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
} = props; } = props;
const [expandedIssues, setExpandedIssues] = useState<string[]>([]); const [expandedIssues, setExpandedIssues] = useState<string[]>([]);
const [currentProjectId, setCurrentProjectId] = useState<string | null>(null);
const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false);
@ -59,7 +57,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, cycleId, moduleId } = router.query; const { cycleId, moduleId } = router.query;
const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
@ -266,10 +264,10 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
); );
const handleScroll = () => { const handleScroll = () => {
if (containerRef.current) { if (!containerRef.current) return;
const scrollLeft = containerRef.current.scrollLeft; const scrollLeft = containerRef.current.scrollLeft;
setIsScrolled(scrollLeft > 0); setIsScrolled(scrollLeft > 0);
}
}; };
useEffect(() => { useEffect(() => {
@ -288,11 +286,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
return ( return (
<> <>
<IssuePeekOverview
projectId={currentProjectId ?? ""}
workspaceSlug={workspaceSlug?.toString() ?? ""}
readOnly={disableUserActions}
/>
<div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200"> <div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200">
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">
<div ref={containerRef} className="flex max-h-full h-full overflow-y-auto"> <div ref={containerRef} className="flex max-h-full h-full overflow-y-auto">
@ -302,7 +295,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
<div <div
className="relative flex flex-col h-max w-full bg-custom-background-100 z-[2]" className="relative flex flex-col h-max w-full bg-custom-background-100 z-[2]"
style={{ style={{
boxShadow: isScrolled ? "8px -9px 12px rgba(0, 0, 0, 0.15)" : "", boxShadow: isScrolled ? "4px -9px 12px rgba(0, 0, 0, 0.1)" : "",
}} }}
> >
<div className="flex items-center text-sm font-medium z-[2] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-100"> <div className="flex items-center text-sm font-medium z-[2] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-100">
@ -312,14 +305,13 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
<span className="flex items-center px-4 py-2.5 h-full w-full flex-grow">Issue</span> <span className="flex items-center px-4 py-2.5 h-full w-full flex-grow">Issue</span>
</div> </div>
{issues.map((issue: IIssue, index) => ( {issues.map((issue, index) => (
<SpreadsheetIssuesColumn <SpreadsheetIssuesColumn
key={`${issue.id}_${index}`} key={`${issue.id}_${index}`}
issue={issue} issue={issue}
projectId={issue.project_detail.id} projectId={issue.project_detail.id}
expandedIssues={expandedIssues} expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues} setExpandedIssues={setExpandedIssues}
setCurrentProjectId={setCurrentProjectId}
properties={displayProperties} properties={displayProperties}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
@ -361,7 +353,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
</div> </div>
<div className="border-t border-custom-border-100"> <div className="border-t border-custom-border-100">
<div className="mb-3 z-50 sticky bottom-0 left-0"> {/* <div className="mb-3 z-50 sticky bottom-0 left-0">
<ListInlineCreateIssueForm <ListInlineCreateIssueForm
isOpen={isInlineCreateIssueFormOpen} isOpen={isInlineCreateIssueFormOpen}
handleClose={() => setIsInlineCreateIssueFormOpen(false)} handleClose={() => setIsInlineCreateIssueFormOpen(false)}
@ -370,7 +362,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
...(moduleId && { module: moduleId.toString() }), ...(moduleId && { module: moduleId.toString() }),
}} }}
/> />
</div> </div> */}
{type === "issue" {type === "issue"
? !disableUserActions && ? !disableUserActions &&

View File

@ -1,5 +1,5 @@
export * from "./blocks"; export * from "./blocks";
export * from "./cycle-root"; export * from "./cycle-root";
export * from "./module-root"; export * from "./module-root";
export * from "./project-root";
export * from "./project-view-root"; export * from "./project-view-root";
export * from "./root";

View File

@ -11,7 +11,7 @@ import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "comp
// types // types
import { IIssueUnGroupedStructure } from "store/issue"; import { IIssueUnGroupedStructure } from "store/issue";
export const GanttLayout: React.FC = observer(() => { export const ProjectGanttLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;

View File

@ -11,6 +11,8 @@ import { observer } from "mobx-react-lite";
// mobx // mobx
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
// types
import { IIssue } from "types";
export interface IGroupByKanBan { export interface IGroupByKanBan {
issues: any; issues: any;
@ -20,7 +22,7 @@ export interface IGroupByKanBan {
list: any; list: any;
listKey: string; listKey: string;
isDragDisabled: boolean; isDragDisabled: boolean;
handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void; handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
display_properties: any; display_properties: any;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;

View File

@ -9,10 +9,10 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { import {
ListLayout, ListLayout,
CalendarLayout, CalendarLayout,
GanttLayout, ProjectGanttLayout,
KanBanLayout, KanBanLayout,
ProjectAppliedFiltersRoot, ProjectAppliedFiltersRoot,
SpreadsheetLayout, ProjectSpreadsheetLayout,
} from "components/issues"; } from "components/issues";
export const ProjectLayoutRoot: React.FC = observer(() => { export const ProjectLayoutRoot: React.FC = observer(() => {
@ -26,6 +26,7 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore } = useMobxStore(); const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore } = useMobxStore();
// TODO: remove fetch logic from here
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_ISSUES` : null, workspaceSlug && projectId ? `PROJECT_ISSUES` : null,
async () => { async () => {
@ -56,9 +57,9 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
) : activeLayout === "calendar" ? ( ) : activeLayout === "calendar" ? (
<CalendarLayout /> <CalendarLayout />
) : activeLayout === "gantt_chart" ? ( ) : activeLayout === "gantt_chart" ? (
<GanttLayout /> <ProjectGanttLayout />
) : activeLayout === "spreadsheet" ? ( ) : activeLayout === "spreadsheet" ? (
<SpreadsheetLayout /> <ProjectSpreadsheetLayout />
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
export * from "./cycle-root"; export * from "./cycle-root";
export * from "./module-root"; export * from "./module-root";
export * from "./project-root";
export * from "./project-view-root"; export * from "./project-view-root";
export * from "./root";

View File

@ -0,0 +1,62 @@
import React, { useCallback } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useProjectDetails from "hooks/use-project-details";
// components
import { SpreadsheetView } from "components/core";
import { IssuePeekOverview } from "components/issues";
// types
import { IIssue, IIssueDisplayFilterOptions } from "types";
export const ProjectSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { projectDetails } = useProjectDetails();
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
const issues = issueStore.getIssues;
const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
display_filters: {
...updatedDisplayFilter,
},
});
},
[issueFilterStore, projectId, workspaceSlug]
);
const updateIssue = (group_by: string | null, sub_group_by: string | null, issue: IIssue) => {
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
};
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
return (
<>
<IssuePeekOverview
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug?.toString() ?? ""}
readOnly={!isAllowed}
/>
<SpreadsheetView
displayProperties={issueFilterStore.userDisplayProperties}
displayFilters={issueFilterStore.userDisplayFilters}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues as IIssue[]}
handleIssueAction={() => {}}
handleUpdateIssue={() => {}}
disableUserActions={false}
/>
</>
);
});

View File

@ -1,161 +0,0 @@
import React, { useCallback, useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useUser from "hooks/use-user";
import useProjectDetails from "hooks/use-project-details";
// components
import { SpreadsheetColumns, SpreadsheetIssues } from "components/core";
import { IssuePeekOverview } from "components/issues";
// ui
import { CustomMenu } from "components/ui";
import { Spinner } from "@plane/ui";
// icon
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "types";
import { IIssueUnGroupedStructure } from "store/issue";
// constants
import { SPREADSHEET_COLUMN } from "constants/spreadsheet";
export const SpreadsheetLayout: React.FC = observer(() => {
const [expandedIssues, setExpandedIssues] = useState<string[]>([]);
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { user } = useUser();
const { projectDetails } = useProjectDetails();
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
const issues = issueStore.getIssues;
const issueDisplayProperties = issueFilterStore.userDisplayProperties;
const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
display_filters: {
...updatedDisplayFilter,
},
});
},
[issueFilterStore, projectId, workspaceSlug]
);
const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
const columnData = SPREADSHEET_COLUMN.map((column) => ({
...column,
isActive: issueDisplayProperties
? column.propertyName === "labels"
? issueDisplayProperties[column.propertyName as keyof IIssueDisplayProperties]
: column.propertyName === "title"
? true
: issueDisplayProperties[column.propertyName as keyof IIssueDisplayProperties]
: false,
}));
const gridTemplateColumns = columnData
.filter((column) => column.isActive)
.map((column) => column.colSize)
.join(" ");
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
return (
<>
<IssuePeekOverview
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug?.toString() ?? ""}
readOnly={!isAllowed}
/>
<div className="h-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100">
<div className="sticky z-[2] top-0 border-b border-custom-border-200 bg-custom-background-90 w-full min-w-max">
<SpreadsheetColumns
columnData={columnData}
displayFilters={issueFilterStore.userDisplayFilters}
gridTemplateColumns={gridTemplateColumns}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
/>
</div>
{issues ? (
<div className="flex flex-col h-full w-full bg-custom-background-100 rounded-sm ">
{(issues as IIssueUnGroupedStructure).map((issue: IIssue, index) => (
<SpreadsheetIssues
key={`${issue.id}_${index}`}
index={index}
issue={issue}
expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues}
gridTemplateColumns={gridTemplateColumns}
properties={issueDisplayProperties}
handleIssueAction={() => {}}
disableUserActions={!isAllowed}
user={user}
userAuth={{
isViewer: projectDetails?.member_role === 5,
isGuest: projectDetails?.member_role === 10,
isMember: projectDetails?.member_role === 15,
isOwner: projectDetails?.member_role === 20,
}}
/>
))}
<div
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-200 w-full min-w-max"
style={{ gridTemplateColumns }}
>
{type === "issue" ? (
<button
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-200 w-full"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
) : (
isAllowed && (
<CustomMenu
className="sticky left-0 z-[1]"
customButton={
<button
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-200 w-full"
type="button"
>
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
}
position="left"
optionsClassName="left-5 !w-36"
noBorder
>
<CustomMenu.MenuItem
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
Create new
</CustomMenu.MenuItem>
{true && <CustomMenu.MenuItem onClick={() => {}}>Add an existing issue</CustomMenu.MenuItem>}
</CustomMenu>
)
)}
</div>
</div>
) : (
<Spinner />
)}
</div>
</>
);
});

View File

@ -99,13 +99,13 @@ class IssueStore implements IIssueStore {
return this.issues?.[projectId]?.[issueType] || null; return this.issues?.[projectId]?.[issueType] || null;
} }
// TODO: params order is different from what is present in components
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => { updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
const projectId: string | null = issue?.project; const projectId: string | null = issue?.project;
const issueType = this.getIssueType; const issueType = this.getIssueType;
if (!projectId || !issueType) return null; if (!projectId || !issueType) return null;
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null = let issues = this.getIssues;
this.getIssues;
if (!issues) return null; if (!issues) return null;
if (issueType === "grouped" && group_id) { if (issueType === "grouped" && group_id) {