Skip to content

feat: spreadsheet view #1369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3d01051
feat: spreadsheet view
anmolsinghbhatia Jun 2, 2023
a6d8c65
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 2, 2023
2b363a1
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 8, 2023
2bb7374
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 14, 2023
6c8cd38
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 14, 2023
1adae3d
fix: fix scroll and overflow issues, feat: updated issue properties c…
anmolsinghbhatia Jun 15, 2023
e56a758
feat: sub-issue toggle and sub-issue hook added, chore: code refactor
anmolsinghbhatia Jun 16, 2023
16cf91e
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 16, 2023
a6dab75
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 20, 2023
255cbf1
fix: only render parent issue
anmolsinghbhatia Jun 20, 2023
657a195
feat: sub issue fetching hook updated and nested sub issue added, cho…
anmolsinghbhatia Jun 21, 2023
cbf1c3b
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 21, 2023
e6c68d9
style: title sticky to left on scroll and column styling
anmolsinghbhatia Jun 21, 2023
ab23d78
fix: tooltip , filter and view z-index fix
anmolsinghbhatia Jun 22, 2023
c2a8fd4
feat: spreadsheet view column sorting, fix: sticky scroll issue fix
anmolsinghbhatia Jun 22, 2023
9fe9dd8
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 22, 2023
2b5e07f
feat: updated issue view filter for spreadsheet view
anmolsinghbhatia Jun 22, 2023
d2bdaee
style: spreadsheet view column
anmolsinghbhatia Jun 22, 2023
e0e9fb5
feat: double click to edit title
anmolsinghbhatia Jun 22, 2023
7573026
fix: estimate sorting fix
anmolsinghbhatia Jun 22, 2023
ee7f573
style: spreadsheet view columns
anmolsinghbhatia Jun 23, 2023
ee2d9ee
fix: spreadsheet view mutation, feat: edit , copy and delete option a…
anmolsinghbhatia Jun 23, 2023
7e7542b
Merge branch 'develop' of github.com:makeplane/plane into feat/spread…
anmolsinghbhatia Jun 23, 2023
26b790f
fix: edit sub issue fix
anmolsinghbhatia Jun 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/app/components/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from "./board-view";
export * from "./calendar-view";
export * from "./gantt-chart-view";
export * from "./list-view";
export * from "./spreadsheet-view";
export * from "./sidebar";
export * from "./bulk-delete-issues-modal";
export * from "./existing-issues-list-modal";
Expand Down
24 changes: 20 additions & 4 deletions apps/app/components/core/issues-view-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Popover, Transition } from "@headlessui/react";
// components
import { SelectFilters } from "components/views";
// ui
import { CustomMenu, ToggleSwitch } from "components/ui";
import { CustomMenu, Icon, ToggleSwitch } from "components/ui";
// icons
import {
ChevronDownIcon,
Expand Down Expand Up @@ -83,6 +83,15 @@ export const IssuesFilterView: React.FC = () => {
>
<CalendarDaysIcon className="h-4 w-4 text-brand-secondary" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-brand-surface-2 ${
issueView === "spreadsheet" ? "bg-brand-surface-2" : ""
}`}
onClick={() => setIssueView("spreadsheet")}
>
<Icon iconName="table_chart" className="text-brand-secondary" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded outline-none duration-300 hover:bg-brand-surface-2 ${
Expand Down Expand Up @@ -146,10 +155,10 @@ export const IssuesFilterView: React.FC = () => {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute right-0 z-20 mt-1 w-screen max-w-xs transform rounded-lg border border-brand-base bg-brand-surface-1 p-3 shadow-lg">
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-brand-base bg-brand-surface-1 p-3 shadow-lg">
<div className="relative divide-y-2 divide-brand-base">
<div className="space-y-4 pb-3 text-xs">
{issueView !== "calendar" && (
{issueView !== "calendar" && issueView !== "spreadsheet" && (
<>
<div className="flex items-center justify-between">
<h4 className="text-brand-secondary">Group by</h4>
Expand Down Expand Up @@ -221,7 +230,7 @@ export const IssuesFilterView: React.FC = () => {
</CustomMenu>
</div>

{issueView !== "calendar" && (
{issueView !== "calendar" && issueView !== "spreadsheet" && (
<>
<div className="flex items-center justify-between">
<h4 className="text-brand-secondary">Show empty states</h4>
Expand Down Expand Up @@ -252,6 +261,13 @@ export const IssuesFilterView: React.FC = () => {
{Object.keys(properties).map((key) => {
if (key === "estimate" && !isEstimateActive) return null;

if (
(issueView === "spreadsheet" && key === "sub_issue_count") ||
key === "attachment_count" ||
key === "link"
)
return null;

return (
<button
key={key}
Expand Down
16 changes: 15 additions & 1 deletion apps/app/components/core/issues-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
import useUserAuth from "hooks/use-user-auth";
// components
import { AllLists, AllBoards, FilterList, CalendarView, GanttChartView } from "components/core";
import {
AllLists,
AllBoards,
FilterList,
CalendarView,
GanttChartView,
SpreadsheetView,
} from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
import { CreateUpdateViewModal } from "components/views";
import { CycleIssuesGanttChartView, TransferIssues, TransferIssuesModal } from "components/cycles";
Expand Down Expand Up @@ -563,6 +570,13 @@ export const IssuesView: React.FC<Props> = ({
user={user}
userAuth={memberRole}
/>
) : issueView === "spreadsheet" ? (
<SpreadsheetView
handleEditIssue={handleEditIssue}
handleDeleteIssue={handleDeleteIssue}
user={user}
userAuth={memberRole}
/>
) : (
issueView === "gantt_chart" && <GanttChartView />
)}
Expand Down
4 changes: 4 additions & 0 deletions apps/app/components/core/spreadsheet-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./spreadsheet-view";
export * from "./single-issue";
export * from "./spreadsheet-columns";
export * from "./spreadsheet-issues";
266 changes: 266 additions & 0 deletions apps/app/components/core/spreadsheet-view/single-issue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import React, { useCallback } from "react";

import Link from "next/link";
import { useRouter } from "next/router";

import { mutate } from "swr";

// components
import {
ViewAssigneeSelect,
ViewDueDateSelect,
ViewEstimateSelect,
ViewPrioritySelect,
ViewStateSelect,
} from "components/issues";
// icons
import { CustomMenu, Icon } from "components/ui";
import { LinkIcon, PencilIcon, TrashIcon, XMarkIcon } from "@heroicons/react/24/outline";
// hooks
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
import useToast from "hooks/use-toast";
// services
import issuesService from "services/issues.service";
// constant
import {
CYCLE_ISSUES_WITH_PARAMS,
MODULE_ISSUES_WITH_PARAMS,
PROJECT_ISSUES_LIST_WITH_PARAMS,
VIEW_ISSUES,
} from "constants/fetch-keys";
// types
import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types";
// helper
import { copyTextToClipboard } from "helpers/string.helper";

type Props = {
issue: IIssue;
expanded: boolean;
handleToggleExpand: (issueId: string) => void;
properties: Properties;
handleEditIssue: () => void;
handleDeleteIssue: (issue: IIssue) => void;
gridTemplateColumns: string;
user: ICurrentUserResponse | undefined;
userAuth: UserAuth;
nestingLevel: number;
};

export const SingleSpreadsheetIssue: React.FC<Props> = ({
issue,
expanded,
handleToggleExpand,
properties,
handleEditIssue,
handleDeleteIssue,
gridTemplateColumns,
user,
userAuth,
nestingLevel,
}) => {
const router = useRouter();

const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;

const { params } = useSpreadsheetIssuesView();

const { setToastAlert } = useToast();

const partialUpdateIssue = useCallback(
(formData: Partial<IIssue>, issueId: string) => {
if (!workspaceSlug || !projectId) return;

const fetchKey = cycleId
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)
: moduleId
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
: viewId
? VIEW_ISSUES(viewId.toString(), params)
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);

mutate<IIssue[]>(
fetchKey,
(prevData) =>
(prevData ?? []).map((p) => {
if (p.id === issueId) {
return {
...p,
...formData,
};
}
return p;
}),
false
);

issuesService
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, formData, user)
.then(() => {
mutate(fetchKey);
})
.catch((error) => {
console.log(error);
});
},
[workspaceSlug, projectId, cycleId, moduleId, viewId, params, user]
);

const handleCopyText = () => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});
});
};

const paddingLeft = `${nestingLevel * 68}px`;

const isNotAllowed = userAuth.isGuest || userAuth.isViewer;

return (
<div
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
style={{ gridTemplateColumns }}
>
<div className="flex gap-1.5 items-center px-4 sticky left-0 z-10 text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full">
<span className="flex gap-1 items-center" style={issue.parent ? { paddingLeft } : {}}>
{properties.key && (
<>
<div className="flex items-center cursor-pointer text-xs text-center hover:text-brand-base w-14 ">
{issue.project_detail?.identifier}-{issue.sequence_id}
</div>
</>
)}

<div className="h-5 w-5">
{issue.sub_issues_count > 0 && (
<button
className="h-5 w-5 hover:bg-brand-surface-1 hover:text-brand-base rounded-sm"
onClick={() => handleToggleExpand(issue.id)}
>
<Icon iconName="chevron_right" className={`${expanded ? "rotate-90" : ""}`} />
</button>
)}
</div>
</span>
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
<a className="truncate text-brand-base cursor-pointer w-full text-[0.825rem]">
{issue.name}
</a>
</Link>
</div>
{properties.state && (
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewStateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="left"
customButton
user={user}
isNotAllowed={isNotAllowed}
/>
</div>
)}
{properties.priority && (
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewPrioritySelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="left"
noBorder
user={user}
isNotAllowed={isNotAllowed}
/>
</div>
)}
{properties.assignee && (
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewAssigneeSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="left"
customButton
user={user}
isNotAllowed={isNotAllowed}
/>
</div>
)}
{properties.labels ? (
issue.label_details.length > 0 ? (
<div className="flex items-center gap-2 text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
{issue.label_details.slice(0, 4).map((label, index) => (
<div className={`flex h-4 w-4 rounded-full ${index ? "-ml-3.5" : ""}`}>
<span
className={`h-4 w-4 flex-shrink-0 rounded-full border group-hover:bg-brand-surface-2 border-brand-base
`}
style={{
backgroundColor: label?.color && label.color !== "" ? label.color : "#000000",
}}
/>
</div>
))}
{issue.label_details.length > 4 ? <span>+{issue.label_details.length - 4}</span> : null}
</div>
) : (
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
No Labels
</div>
)
) : (
""
)}
{properties.due_date && (
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
noBorder
user={user}
isNotAllowed={isNotAllowed}
/>
</div>
)}
{properties.estimate && (
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="left"
user={user}
isNotAllowed={isNotAllowed}
/>
</div>
)}
<div className="absolute top-2.5 right-2.5 z-30 cursor-pointer opacity-0 group-hover:opacity-100">
{!isNotAllowed && (
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={handleEditIssue}>
<div className="flex items-center justify-start gap-2">
<PencilIcon className="h-4 w-4" />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
<div className="flex items-center justify-start gap-2">
<TrashIcon className="h-4 w-4" />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}>
<div className="flex items-center justify-start gap-2">
<LinkIcon className="h-4 w-4" />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
)}
</div>
</div>
);
};
Loading