From 69c23700fdf8161ecf622683fe63a0b59b757062 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Wed, 4 Oct 2023 15:28:06 +0530 Subject: [PATCH] chore: issue list layout --- web/components/core/views/all-views.tsx | 13 +- web/components/issues/issue-layouts/index.ts | 1 + .../issue-layouts/kanban/properties.tsx | 6 - .../issues/issue-layouts/list/block.tsx | 41 +++++ .../issues/issue-layouts/list/default.tsx | 132 ++++++++++++++ .../issue-layouts/list/headers/assignee.tsx | 33 ++++ .../issue-layouts/list/headers/created_by.tsx | 31 ++++ .../list/headers/group-by-card.tsx | 43 +++++ .../list/headers/group-by-root.tsx | 30 ++++ .../issue-layouts/list/headers/label.tsx | 24 +++ .../issue-layouts/list/headers/priority.tsx | 51 ++++++ .../list/headers/state-group.tsx | 34 ++++ .../issue-layouts/list/headers/state.tsx | 31 ++++ .../issues/issue-layouts/list/index.ts | 1 + .../issues/issue-layouts/list/properties.tsx | 166 ++++++++++++++++++ .../issues/issue-layouts/list/root.tsx | 30 ++++ 16 files changed, 659 insertions(+), 8 deletions(-) create mode 100644 web/components/issues/issue-layouts/list/block.tsx create mode 100644 web/components/issues/issue-layouts/list/default.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/assignee.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/created_by.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/group-by-card.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/group-by-root.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/label.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/priority.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/state-group.tsx create mode 100644 web/components/issues/issue-layouts/list/headers/state.tsx create mode 100644 web/components/issues/issue-layouts/list/index.ts create mode 100644 web/components/issues/issue-layouts/list/properties.tsx create mode 100644 web/components/issues/issue-layouts/list/root.tsx diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index 8998fed8643..04e501aae1e 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -6,7 +6,14 @@ import useSWR from "swr"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { AppliedFiltersRoot, CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues"; +import { + AppliedFiltersRoot, + ListLayout, + CalendarLayout, + GanttLayout, + KanBanLayout, + SpreadsheetLayout, +} from "components/issues"; export const AllViews: React.FC = observer(() => { const router = useRouter(); @@ -42,7 +49,9 @@ export const AllViews: React.FC = observer(() => {
- {activeLayout === "kanban" ? ( + {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( ) : activeLayout === "calendar" ? ( diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index 8840e08dfdb..dad8ff03323 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -1,3 +1,4 @@ +export * from "./list"; export * from "./calendar"; export * from "./filters"; export * from "./gantt"; diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx index 03df73fcaf9..4c58a8df7b4 100644 --- a/web/components/issues/issue-layouts/kanban/properties.tsx +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -197,9 +197,3 @@ export const KanBanProperties: React.FC = observer( ); } ); - -created_on: true; -updated_on: true; -due_date: true; - -key: true; diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx new file mode 100644 index 00000000000..a54e47a010d --- /dev/null +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -0,0 +1,41 @@ +// components +import { KanBanProperties } from "./properties"; + +interface IssueBlockProps { + columnId: string; + issues: any; + handleIssues?: (group_by: string | null, issue: any) => void; + display_properties: any; +} + +export const IssueBlock = ({ columnId, issues, handleIssues, display_properties }: IssueBlockProps) => ( + <> + {issues && issues.length > 0 ? ( + <> + {issues.map((issue: any, index: any) => ( +
+ {display_properties && display_properties?.key && ( +
ONE-{issue.sequence_id}
+ )} +
{issue.name}
+
+ +
+
+ ))} + + ) : ( +
+ No issues are available +
+ )} + +); diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx new file mode 100644 index 00000000000..16c6a35d844 --- /dev/null +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -0,0 +1,132 @@ +import React from "react"; +// components +import { KanBanGroupByHeaderRoot } from "./headers/group-by-root"; +import { IssueBlock } from "./block"; +// constants +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES, getValueFromObject } from "constants/issue"; +// mobx +import { observer } from "mobx-react-lite"; +// mobx +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IGroupByKanBan { + issues: any; + group_by: string | null; + list: any; + listKey: string; + handleIssues?: (group_by: string | null, issue: any) => void; + display_properties: any; +} + +const GroupByKanBan: React.FC = observer( + ({ issues, group_by, list, listKey, handleIssues, display_properties }) => ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+
+ +
+
+ {issues && ( + + )} +
+
+ ))} +
+ ) +); + +export interface IKanBan { + issues: any; + group_by: string | null; + handleDragDrop?: (result: any) => void | undefined; + handleIssues?: (group_by: string | null, issue: any) => void; + display_properties: any; +} + +export const List: React.FC = observer(({ issues, group_by, handleIssues, display_properties }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + return ( +
+ {group_by && group_by === "state" && ( + + )} + + {group_by && group_by === "state_detail.group" && ( + + )} + + {group_by && group_by === "priority" && ( + + )} + + {group_by && group_by === "labels" && ( + + )} + + {group_by && group_by === "assignees" && ( + + )} + + {group_by && group_by === "created_by" && ( + + )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/list/headers/assignee.tsx b/web/components/issues/issue-layouts/list/headers/assignee.tsx new file mode 100644 index 00000000000..0c46dc0297f --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/assignee.tsx @@ -0,0 +1,33 @@ +// mobx +import { observer } from "mobx-react-lite"; +// components +import { HeaderGroupByCard } from "./group-by-card"; +import { Avatar } from "components/ui"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IAssigneesHeader { + column_id: string; + issues_count: number; +} + +export const Icon = ({ user }: any) => ; + +export const AssigneesHeader: React.FC = observer(({ column_id, issues_count }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; + + return ( + <> + {assignee && ( + } + title={assignee?.member?.display_name || ""} + count={issues_count} + /> + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/list/headers/created_by.tsx b/web/components/issues/issue-layouts/list/headers/created_by.tsx new file mode 100644 index 00000000000..92b074936ee --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/created_by.tsx @@ -0,0 +1,31 @@ +// mobx +import { observer } from "mobx-react-lite"; +// components +import { HeaderGroupByCard } from "./group-by-card"; +import { Icon } from "./assignee"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface ICreatedByHeader { + column_id: string; + issues_count: number; +} + +export const CreatedByHeader: React.FC = observer(({ column_id, issues_count }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; + + return ( + <> + {createdBy && ( + } + title={createdBy?.member?.display_name || ""} + count={issues_count} + /> + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx new file mode 100644 index 00000000000..3e4cba68a91 --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -0,0 +1,43 @@ +import React from "react"; +// lucide icons +import { Plus, Minimize2, Maximize2, Circle } from "lucide-react"; +// mobx +import { observer } from "mobx-react-lite"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +interface IHeaderGroupByCard { + icon?: React.ReactNode; + title: string; + count: number; +} + +export const HeaderGroupByCard = observer(({ icon, title, count }: IHeaderGroupByCard) => { + const verticalAlignPosition = false; + + return ( +
+
+ {icon ? icon : } +
+ +
+
+ {title} +
+
+ {count || 0} +
+
+ + {/*
+ +
*/} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/list/headers/group-by-root.tsx b/web/components/issues/issue-layouts/list/headers/group-by-root.tsx new file mode 100644 index 00000000000..f4a4f382004 --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/group-by-root.tsx @@ -0,0 +1,30 @@ +// components +import { StateHeader } from "./state"; +import { StateGroupHeader } from "./state-group"; +import { AssigneesHeader } from "./assignee"; +import { PriorityHeader } from "./priority"; +import { LabelHeader } from "./label"; +import { CreatedByHeader } from "./created_by"; +// mobx +import { observer } from "mobx-react-lite"; + +export interface IKanBanGroupByHeaderRoot { + column_id: string; + group_by: string | null; + issues_count: number; +} + +export const KanBanGroupByHeaderRoot: React.FC = observer( + ({ column_id, group_by, issues_count }) => ( + <> + {group_by && group_by === "state" && } + {group_by && group_by === "state_detail.group" && ( + + )} + {group_by && group_by === "priority" && } + {group_by && group_by === "labels" && } + {group_by && group_by === "assignees" && } + {group_by && group_by === "created_by" && } + + ) +); diff --git a/web/components/issues/issue-layouts/list/headers/label.tsx b/web/components/issues/issue-layouts/list/headers/label.tsx new file mode 100644 index 00000000000..d7a6c0253ab --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/label.tsx @@ -0,0 +1,24 @@ +// mobx +import { observer } from "mobx-react-lite"; +// components +import { HeaderGroupByCard } from "./group-by-card"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface ILabelHeader { + column_id: string; + issues_count: number; +} + +const Icon = ({ color }: any) => ( +
+); + +export const LabelHeader: React.FC = observer(({ column_id, issues_count }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null; + + return <>{label && } title={label?.name || ""} count={issues_count} />}; +}); diff --git a/web/components/issues/issue-layouts/list/headers/priority.tsx b/web/components/issues/issue-layouts/list/headers/priority.tsx new file mode 100644 index 00000000000..6bc29bb4806 --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/priority.tsx @@ -0,0 +1,51 @@ +// mobx +import { observer } from "mobx-react-lite"; +// lucide icons +import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; +// components +import { HeaderGroupByCard } from "./group-by-card"; +// constants +import { issuePriorityByKey } from "constants/issue"; + +export interface IPriorityHeader { + column_id: string; + issues_count: number; +} + +const Icon = ({ priority }: any) => ( +
+ {priority === "urgent" ? ( +
+ +
+ ) : priority === "high" ? ( +
+ +
+ ) : priority === "medium" ? ( +
+ +
+ ) : priority === "low" ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+); + +export const PriorityHeader: React.FC = observer(({ column_id, issues_count }) => { + const priority = column_id && issuePriorityByKey(column_id); + + return ( + <> + {priority && ( + } title={priority?.key || ""} count={issues_count} /> + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/list/headers/state-group.tsx b/web/components/issues/issue-layouts/list/headers/state-group.tsx new file mode 100644 index 00000000000..4e916985218 --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/state-group.tsx @@ -0,0 +1,34 @@ +// mobx +import { observer } from "mobx-react-lite"; +// components +import { HeaderGroupByCard } from "./group-by-card"; +import { StateGroupIcon } from "components/icons"; +// constants +import { issueStateGroupByKey } from "constants/issue"; + +export interface IStateGroupHeader { + column_id: string; + issues_count: number; +} + +export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => ( +
+ +
+); + +export const StateGroupHeader: React.FC = observer(({ column_id, issues_count }) => { + const stateGroup = column_id && issueStateGroupByKey(column_id); + + return ( + <> + {stateGroup && ( + } + title={stateGroup?.key || ""} + count={issues_count} + /> + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/list/headers/state.tsx b/web/components/issues/issue-layouts/list/headers/state.tsx new file mode 100644 index 00000000000..4210cffe64c --- /dev/null +++ b/web/components/issues/issue-layouts/list/headers/state.tsx @@ -0,0 +1,31 @@ +// mobx +import { observer } from "mobx-react-lite"; +// components +import { HeaderGroupByCard } from "./group-by-card"; +import { Icon } from "./state-group"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IStateHeader { + column_id: string; + issues_count: number; +} + +export const StateHeader: React.FC = observer(({ column_id, issues_count }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null; + + return ( + <> + {state && ( + } + title={state?.name || ""} + count={issues_count} + /> + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/list/index.ts b/web/components/issues/issue-layouts/list/index.ts new file mode 100644 index 00000000000..1efe34c51ec --- /dev/null +++ b/web/components/issues/issue-layouts/list/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/web/components/issues/issue-layouts/list/properties.tsx b/web/components/issues/issue-layouts/list/properties.tsx new file mode 100644 index 00000000000..b43b50b89c7 --- /dev/null +++ b/web/components/issues/issue-layouts/list/properties.tsx @@ -0,0 +1,166 @@ +// mobx +import { observer } from "mobx-react-lite"; +// lucide icons +import { Layers, Link, Paperclip } from "lucide-react"; +// components +import { IssuePropertyState } from "../properties/state"; +import { IssuePropertyPriority } from "../properties/priority"; +import { IssuePropertyLabels } from "../properties/labels"; +import { IssuePropertyAssignee } from "../properties/assignee"; +import { IssuePropertyEstimates } from "../properties/estimates"; +import { IssuePropertyStartDate } from "../properties/date"; +import { Tooltip } from "components/ui"; + +export interface IKanBanProperties { + columnId: string; + issue: any; + handleIssues?: (group_by: string | null, issue: any) => void; + display_properties: any; +} + +export const KanBanProperties: React.FC = observer( + ({ columnId: group_id, issue, handleIssues, display_properties }) => { + const handleState = (id: string) => { + if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, state: id }); + }; + + const handlePriority = (id: string) => { + if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, priority: id }); + }; + + const handleLabel = (ids: string[]) => { + if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, labels: ids }); + }; + + const handleAssignee = (ids: string[]) => { + if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, assignees: ids }); + }; + + const handleStartDate = (date: string) => { + if (handleIssues) + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, start_date: date }); + }; + + const handleTargetDate = (date: string) => { + if (handleIssues) + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, target_date: date }); + }; + + const handleEstimate = (id: string) => { + if (handleIssues) + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, estimate_point: id }); + }; + + return ( +
+ {/* basic properties */} + {/* state */} + {display_properties && display_properties?.state && ( + handleState(id)} + disabled={false} + /> + )} + + {/* priority */} + {display_properties && display_properties?.priority && ( + handlePriority(id)} + disabled={false} + /> + )} + + {/* label */} + {display_properties && display_properties?.labels && ( + handleLabel(ids)} + disabled={false} + /> + )} + + {/* assignee */} + {display_properties && display_properties?.assignee && ( + handleAssignee(ids)} + disabled={false} + /> + )} + + {/* start date */} + {display_properties && display_properties?.start_date && ( + handleStartDate(date)} + disabled={false} + /> + )} + + {/* target/due date */} + {display_properties && display_properties?.due_date && ( + handleTargetDate(date)} + disabled={false} + /> + )} + + {/* estimates */} + {display_properties && display_properties?.estimate && ( + handleEstimate(id)} + disabled={false} + workspaceSlug={issue?.workspace_detail?.slug || null} + projectId={issue?.project_detail?.id || null} + /> + )} + + {/* extra render properties */} + {/* sub-issues */} + {display_properties && display_properties?.sub_issue_count && ( + +
+
+ +
+
{issue.sub_issues_count}
+
+
+ )} + + {/* attachments */} + {display_properties && display_properties?.attachment_count && ( + +
+
+ +
+
{issue.attachment_count}
+
+
+ )} + + {/* link */} + {display_properties && display_properties?.link && ( + +
+
+ +
+
{issue.link_count}
+
+
+ )} +
+ ); + } +); diff --git a/web/components/issues/issue-layouts/list/root.tsx b/web/components/issues/issue-layouts/list/root.tsx new file mode 100644 index 00000000000..d3b7d0d3ac9 --- /dev/null +++ b/web/components/issues/issue-layouts/list/root.tsx @@ -0,0 +1,30 @@ +import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { List } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IListLayout {} + +export const ListLayout: React.FC = observer(() => { + const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const updateIssue = (group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, null, issue); + }; + + return ( +
+ +
+ ); +});