forked from github/plane
refactor: spreadsheet layout
This commit is contained in:
parent
8aebf0bbd2
commit
0c13d05e27
@ -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 },
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
|
||||||
setIsScrolled(scrollLeft > 0);
|
const scrollLeft = containerRef.current.scrollLeft;
|
||||||
}
|
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 &&
|
||||||
|
@ -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";
|
|
||||||
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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";
|
|
||||||
|
@ -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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user