Skip to content
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

Implementing list view #2195

Merged
merged 2 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions web/components/core/views/list-view/single-issue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ export const SingleListIssue: React.FC<Props> = ({
const isNotAllowed =
userAuth.isGuest || userAuth.isViewer || disableUserActions || isArchivedIssues;

console.log("properties", properties);

return (
<>
<ContextMenu
Expand Down
41 changes: 41 additions & 0 deletions web/components/issue-layouts/list/group-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { FC } from "react";
// lib
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";

export interface IIssueListGroupHeader {
groupId: string;
groupBy: string;
}

export const IssueListGroupHeader: FC<IIssueListGroupHeader> = (props) => {
const { groupId, groupBy } = props;

const { issueView: issueViewStore, issueFilters: issueFilterStore }: RootStore = useMobxStore();

return (
<div>
{groupBy === "state" && <>{issueFilterStore.getProjectStateById(groupId)?.name}</>}
{groupBy === "state_detail.group" && <>{groupId}</>}
{groupBy === "priority" && <>{groupId}</>}
{groupBy === "project" && (
<>{issueFilterStore.workspaceProjects?.find((p) => (p.id = groupId))}</>
)}
{groupBy === "labels" && (
<>{issueFilterStore.projectLabels?.find((p) => p.id === groupId)?.name || " None"}</>
)}
{groupBy === "assignees" && (
<>
{issueFilterStore.projectMembers?.find((p) => p?.member?.id === groupId)?.member
?.display_name || " None"}
</>
)}
{groupBy === "created_by" && (
<>
{issueFilterStore.projectMembers?.find((p) => p?.member?.id === groupId)?.member
?.display_name || " None"}
</>
)}
</div>
);
};
4 changes: 4 additions & 0 deletions web/components/issue-layouts/list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./root";
export * from "./list";
export * from "./item";
export * from "./group-header";
6 changes: 0 additions & 6 deletions web/components/issue-layouts/list/index.tsx

This file was deleted.

234 changes: 234 additions & 0 deletions web/components/issue-layouts/list/item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import React, { FC, useState } from "react";
import { Tooltip, CustomMenu, ContextMenu } from "components/ui";
// lib
import { useMobxStore } from "lib/mobx/store-provider";
import { IIssue } from "types";
import useUserAuth from "hooks/use-user-auth";
// icons
import {
ClipboardDocumentCheckIcon,
LinkIcon,
PencilIcon,
TrashIcon,
XMarkIcon,
ArrowTopRightOnSquareIcon,
PaperClipIcon,
} from "@heroicons/react/24/outline";
// components
import { LayerDiagonalIcon } from "components/icons";
import {
ViewAssigneeSelect,
ViewDueDateSelect,
ViewEstimateSelect,
ViewIssueLabel,
ViewPrioritySelect,
ViewStartDateSelect,
ViewStateSelect,
} from "components/issues";

export interface IIssueListItem {
issue: IIssue;
}

export const IssueListItem: FC<IIssueListItem> = (props) => {
const { issue } = props;
// store
const { user: userStore, issueFilters: issueFilterStore } = useMobxStore();
const displayProperties = issueFilterStore.userFilters?.display_properties;
console.log("userStore", userStore);
// context menu
const [contextMenu, setContextMenu] = useState(false);
const [contextMenuPosition, setContextMenuPosition] = useState<React.MouseEvent | null>(null);
const { user: userAuth } = useUserAuth();

// const isNotAllowed =
// userAuth?.isGuest || userAuth?.isViewer || disableUserActions || isArchivedIssues;

return (
<div>
<>
<ContextMenu
clickEvent={contextMenuPosition}
title="Quick actions"
isOpen={contextMenu}
setIsOpen={setContextMenu}
>
{/* {!isNotAllowed && (
<>
<ContextMenu.Item Icon={PencilIcon} onClick={editIssue}>
Edit issue
</ContextMenu.Item>
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
Make a copy...
</ContextMenu.Item>
<ContextMenu.Item Icon={TrashIcon} onClick={() => handleDeleteIssue(issue)}>
Delete issue
</ContextMenu.Item>
</>
)}
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
Copy issue link
</ContextMenu.Item>
<a href={issuePath} target="_blank" rel="noreferrer noopener">
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
Open issue in new tab
</ContextMenu.Item>
</a> */}
</ContextMenu>
<div
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
onContextMenu={(e) => {
e.preventDefault();
setContextMenu(true);
setContextMenuPosition(e);
}}
>
<div className="flex-grow cursor-pointer min-w-[200px] whitespace-nowrap overflow-hidden overflow-ellipsis">
<div className="group relative flex items-center gap-2">
{/* {properties.key && (
<Tooltip
tooltipHeading="Issue ID"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`}
>
<span className="flex-shrink-0 text-xs text-custom-text-200">
{issue.project_detail?.identifier}-{issue.sequence_id}
</span>
</Tooltip>
)} */}
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
<button
type="button"
className="truncate text-[0.825rem] text-custom-text-100"
onClick={() => {
// if (!isDraftIssues) openPeekOverview(issue);
// if (handleDraftIssueSelect) handleDraftIssueSelect(issue);
}}
>
{issue.name}
</button>
</Tooltip>
</div>
</div>

<div className={`flex flex-shrink-0 items-center gap-2 text-xs `}>
{displayProperties?.priority && (
<ViewPrioritySelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="right"
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{displayProperties?.state && (
<ViewStateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="right"
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{displayProperties?.start_date && issue.start_date && (
<ViewStartDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{displayProperties?.due_date && issue.target_date && (
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{displayProperties?.labels && (
<ViewIssueLabel labelDetails={issue.label_details} maxRender={3} />
)}
{displayProperties?.assignee && (
<ViewAssigneeSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="right"
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{displayProperties?.estimate && issue.estimate_point !== null && (
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="right"
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{displayProperties?.sub_issue_count && issue.sub_issues_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
{issue.sub_issues_count}
</div>
</Tooltip>
</div>
)}
{displayProperties?.link && issue.link_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LinkIcon className="h-3.5 w-3.5" />
{issue.link_count}
</div>
</Tooltip>
</div>
)}
{displayProperties?.attachment_count && issue.attachment_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
{issue.attachment_count}
</div>
</Tooltip>
</div>
)}
{/* {type && !isNotAllowed && (
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={editIssue}>
<div className="flex items-center justify-start gap-2">
<PencilIcon className="h-4 w-4" />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
{type !== "issue" && removeIssue && (
<CustomMenu.MenuItem onClick={removeIssue}>
<div className="flex items-center justify-start gap-2">
<XMarkIcon className="h-4 w-4" />
<span>Remove from {type}</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>
</>
</div>
);
};
18 changes: 18 additions & 0 deletions web/components/issue-layouts/list/list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { FC } from "react";
import { IIssue } from "types";
import { IssueListItem } from "./item";

export interface IIssueListView {
issues: IIssue[];
}

export const IssueListView: FC<IIssueListView> = (props) => {
const { issues = [] } = props;
return (
<div>
{issues.map((issue) => (
<IssueListItem issue={issue} />
))}
</div>
);
};
44 changes: 44 additions & 0 deletions web/components/issue-layouts/list/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import { Disclosure } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// components
import { IssueListView } from "./list";
import { IssueListGroupHeader } from "./group-header";

export const IssueListViewRoot = observer(() => {
const { issueView: issueViewStore, issueFilters: issueFilterStore }: RootStore = useMobxStore();
console.log("issueViewStore", issueViewStore);
console.log("userFilters", issueFilterStore.userFilters);
console.log("issueFilterStore", issueFilterStore);

return (
<div className="relative w-full h-full">
{issueViewStore.loader || issueViewStore?.getIssues === null ? (
<div>Loading...</div>
) : (
<>
{Object.keys(issueViewStore?.getIssues).map((groupId) => (
<Disclosure key={groupId}>
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<IssueListGroupHeader
groupId={groupId}
groupBy={issueFilterStore.userFilters?.display_filters["group_by"] || ""}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2">
<IssueListView issues={issueViewStore?.getIssues?.[groupId]}></IssueListView>
</Disclosure.Panel>
</>
)}
</Disclosure>
))}
</>
)}
</div>
);
});
5 changes: 2 additions & 3 deletions web/components/issue-layouts/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DisplayFiltersSelection } from "./display-filters";

import { FilterPreview } from "./filters-preview";

import { IssueListViewRoot } from "./list";
import { IssueListViewRoot } from "./list/root";
import { IssueKanBanViewRoot } from "./kanban";
import { IssueCalendarViewRoot } from "./calendar";
import { IssueSpreadsheetViewRoot } from "./spreadsheet";
Expand All @@ -19,8 +19,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";

export const IssuesRoot = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore } = store;
const { issueFilters: issueFilterStore }: RootStore = useMobxStore();

return (
<div className="w-full h-full relative flex flex-col overflow-hidden">
Expand Down
Loading
Loading