diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx
index 8998fed86..04e501aae 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 6507b6202..52dc274f8 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 03df73fca..4c58a8df7 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 000000000..a54e47a01
--- /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 000000000..16c6a35d8
--- /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 000000000..0c46dc029
--- /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 000000000..92b074936
--- /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 000000000..3e4cba68a
--- /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 000000000..f4a4f3820
--- /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 000000000..d7a6c0253
--- /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 000000000..6bc29bb48
--- /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 000000000..4e9169852
--- /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 000000000..4210cffe6
--- /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 000000000..1efe34c51
--- /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 000000000..b43b50b89
--- /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 000000000..d3b7d0d3a
--- /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 (
+
+
+
+ );
+});