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

chore: implement list view with properties in project issues #2367

Merged
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
13 changes: 11 additions & 2 deletions web/components/core/views/all-views.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -42,7 +49,9 @@ export const AllViews: React.FC = observer(() => {
<div className="relative w-full h-full flex flex-col overflow-auto">
<AppliedFiltersRoot />
<div className="w-full h-full">
{activeLayout === "kanban" ? (
{activeLayout === "list" ? (
<ListLayout />
) : activeLayout === "kanban" ? (
<KanBanLayout />
) : activeLayout === "calendar" ? (
<CalendarLayout />
Expand Down
1 change: 1 addition & 0 deletions web/components/issues/issue-layouts/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./list";
export * from "./calendar";
export * from "./filters";
export * from "./gantt";
Expand Down
6 changes: 0 additions & 6 deletions web/components/issues/issue-layouts/kanban/properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,3 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer(
);
}
);

created_on: true;
updated_on: true;
due_date: true;

key: true;
41 changes: 41 additions & 0 deletions web/components/issues/issue-layouts/list/block.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<div
key={index}
className={`text-sm p-3 shadow-custom-shadow-2xs transition-all bg-custom-background-100 flex items-center flex-wrap gap-3 border-b border-custom-border-200`}
>
{display_properties && display_properties?.key && (
<div className="flex-shrink-0 text-xs text-custom-text-300">ONE-{issue.sequence_id}</div>
)}
<div className="line-clamp-1 text-sm font-medium text-custom-text-100">{issue.name}</div>
<div className="ml-auto flex-shrink-0">
<KanBanProperties
columnId={columnId}
issue={issue}
handleIssues={handleIssues}
display_properties={display_properties}
/>
</div>
</div>
))}
</>
) : (
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
No issues are available
</div>
)}
</>
);
132 changes: 132 additions & 0 deletions web/components/issues/issue-layouts/list/default.tsx
Original file line number Diff line number Diff line change
@@ -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<IGroupByKanBan> = observer(
({ issues, group_by, list, listKey, handleIssues, display_properties }) => (
<div className="relative w-full h-full">
{list &&
list.length > 0 &&
list.map((_list: any) => (
<div className={`flex-shrink-0 flex flex-col`}>
<div className="flex-shrink-0 w-full bg-custom-background-90 py-1 sticky top-0 z-[2] px-3">
<KanBanGroupByHeaderRoot
column_id={getValueFromObject(_list, listKey) as string}
group_by={group_by}
issues_count={issues?.[getValueFromObject(_list, listKey) as string]?.length || 0}
/>
</div>
<div className={`w-full h-full relative transition-all`}>
{issues && (
<IssueBlock
columnId={getValueFromObject(_list, listKey) as string}
issues={issues[getValueFromObject(_list, listKey) as string]}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}
</div>
</div>
))}
</div>
)
);

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<IKanBan> = observer(({ issues, group_by, handleIssues, display_properties }) => {
const { project: projectStore }: RootStore = useMobxStore();

return (
<div className="relative w-full h-full">
{group_by && group_by === "state" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
list={projectStore?.projectStates}
listKey={`id`}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}

{group_by && group_by === "state_detail.group" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
list={ISSUE_STATE_GROUPS}
listKey={`key`}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}

{group_by && group_by === "priority" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
list={ISSUE_PRIORITIES}
listKey={`key`}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}

{group_by && group_by === "labels" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
list={projectStore?.projectLabels}
listKey={`id`}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}

{group_by && group_by === "assignees" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}

{group_by && group_by === "created_by" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
handleIssues={handleIssues}
display_properties={display_properties}
/>
)}
</div>
);
});
33 changes: 33 additions & 0 deletions web/components/issues/issue-layouts/list/headers/assignee.tsx
Original file line number Diff line number Diff line change
@@ -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) => <Avatar user={user} height="22px" width="22px" fontSize="12px" />;

export const AssigneesHeader: React.FC<IAssigneesHeader> = observer(({ column_id, issues_count }) => {
const { project: projectStore }: RootStore = useMobxStore();

const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null;

return (
<>
{assignee && (
<HeaderGroupByCard
icon={<Icon user={assignee?.member} />}
title={assignee?.member?.display_name || ""}
count={issues_count}
/>
)}
</>
);
});
31 changes: 31 additions & 0 deletions web/components/issues/issue-layouts/list/headers/created_by.tsx
Original file line number Diff line number Diff line change
@@ -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<ICreatedByHeader> = observer(({ column_id, issues_count }) => {
const { project: projectStore }: RootStore = useMobxStore();

const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null;

return (
<>
{createdBy && (
<HeaderGroupByCard
icon={<Icon user={createdBy?.member} />}
title={createdBy?.member?.display_name || ""}
count={issues_count}
/>
)}
</>
);
});
43 changes: 43 additions & 0 deletions web/components/issues/issue-layouts/list/headers/group-by-card.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={`flex-shrink-0 relative flex gap-2 p-1.5 ${
verticalAlignPosition ? `flex-col items-center w-[44px]` : `flex-row items-center w-full`
}`}
>
<div className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center">
{icon ? icon : <Circle width={14} strokeWidth={2} />}
</div>

<div className={`flex items-center gap-1 ${verticalAlignPosition ? `flex-col` : `flex-row w-full`}`}>
<div className={`font-medium line-clamp-1 text-custom-text-100 ${verticalAlignPosition ? `vertical-lr` : ``}`}>
{title}
</div>
<div className={`text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}>
{count || 0}
</div>
</div>

{/* <div className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
<Plus width={14} strokeWidth={2} />
</div> */}
</div>
);
});
30 changes: 30 additions & 0 deletions web/components/issues/issue-layouts/list/headers/group-by-root.tsx
Original file line number Diff line number Diff line change
@@ -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<IKanBanGroupByHeaderRoot> = observer(
({ column_id, group_by, issues_count }) => (
<>
{group_by && group_by === "state" && <StateHeader column_id={column_id} issues_count={issues_count} />}
{group_by && group_by === "state_detail.group" && (
<StateGroupHeader column_id={column_id} issues_count={issues_count} />
)}
{group_by && group_by === "priority" && <PriorityHeader column_id={column_id} issues_count={issues_count} />}
{group_by && group_by === "labels" && <LabelHeader column_id={column_id} issues_count={issues_count} />}
{group_by && group_by === "assignees" && <AssigneesHeader column_id={column_id} issues_count={issues_count} />}
{group_by && group_by === "created_by" && <CreatedByHeader column_id={column_id} issues_count={issues_count} />}
</>
)
);
24 changes: 24 additions & 0 deletions web/components/issues/issue-layouts/list/headers/label.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<div className="w-[12px] h-[12px] rounded-full" style={{ backgroundColor: color ? color : "#666" }} />
);

export const LabelHeader: React.FC<ILabelHeader> = observer(({ column_id, issues_count }) => {
const { project: projectStore }: RootStore = useMobxStore();

const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null;

return <>{label && <HeaderGroupByCard icon={<Icon />} title={label?.name || ""} count={issues_count} />}</>;
});
Loading
Loading