Implementing list view

This commit is contained in:
sriram veeraghanta 2023-09-15 15:37:47 +05:30
parent d88a0885d5
commit 9136258926
11 changed files with 370 additions and 11 deletions

View File

@ -198,6 +198,8 @@ export const SingleListIssue: React.FC<Props> = ({
const isNotAllowed =
userAuth.isGuest || userAuth.isViewer || disableUserActions || isArchivedIssues;
console.log("properties", properties);
return (
<>
<ContextMenu

View File

@ -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>
);
};

View File

@ -0,0 +1,4 @@
export * from "./root";
export * from "./list";
export * from "./item";
export * from "./group-header";

View File

@ -1,6 +0,0 @@
import React from "react";
export const IssueListViewRoot = () => {
console.log();
return <div>IssueListViewRoot</div>;
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
});

View File

@ -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";
@ -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">

View File

@ -45,7 +45,7 @@ export const displayPropertyGroupBy: { key: string; title: string }[] = [
{ key: "state", title: "States" },
{ key: "state_detail.group", title: "State Groups" },
{ key: "priority", title: "Priority" },
{ key: "Project", title: "Project" }, // required this on my issues
{ key: "project", title: "Project" }, // required this on my issues
{ key: "labels", title: "Labels" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },

View File

@ -21,6 +21,7 @@ import {
displayProperties,
extraProperties,
} from "./issue_data";
import { IIssueState } from "./Issues";
export type TIssueViews = "my_issues" | "issues" | "modules" | "views" | "cycles";
export type TIssueLayouts = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart";
@ -211,6 +212,8 @@ export interface IIssueFilterStore {
viewId: string,
data: any
) => Promise<any>;
getProjectStateById: (state_id: string) => any;
}
class IssueFilterStore implements IIssueFilterStore {
@ -281,6 +284,9 @@ class IssueFilterStore implements IIssueFilterStore {
userFilters: computed,
// actions
getProjectStateById: action,
getComputedFilters: action,
handleUserFilter: action,
@ -345,6 +351,23 @@ class IssueFilterStore implements IIssueFilterStore {
this.projectId
]?.states;
}
getProjectStateById = (stateId: string) => {
if (!this.workspaceId || !this.projectId) return null;
const states =
this.issueRenderFilters?.workspace_properties?.[this.workspaceId]?.project_properties?.[
this.projectId
]?.states;
let stateInfo: any = null;
Object.keys(states).forEach((stateGroupName) => {
if (states[stateGroupName].find((state: any) => state.id === stateId)) {
stateInfo = states[stateGroupName].find((state: any) => state.id === stateId);
}
});
return stateInfo;
};
get projectLabels() {
if (!this.workspaceId || !this.projectId) return null;
return this.issueRenderFilters?.workspace_properties?.[this.workspaceId]?.project_properties?.[

View File

@ -40,7 +40,7 @@ class ThemeStore {
setTheme = async (_theme: { theme: ICurrentUserSettings }) => {
try {
const currentTheme: string = _theme.theme.theme.toString();
const currentTheme: string = _theme?.theme?.theme?.toString();
// updating the local storage theme value
localStorage.setItem("theme", currentTheme);