chore: created new issue peek overview component and implemented in project issues list view (#2481)

* chore: created new issue peek overview component and implemented in project issues list view

* build: default project props in project, cycles, modules and view layout
This commit is contained in:
guru_sainath 2023-10-18 19:58:05 +05:30 committed by GitHub
parent 704fe155af
commit 9b96e297b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 955 additions and 278 deletions

View File

@ -9,11 +9,14 @@ import { KanBan } from "./default";
// store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// constants
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export interface ICycleKanBanLayout {}
export const CycleKanBanLayout: React.FC = observer(() => {
const {
project: projectStore,
cycleIssue: cycleIssueStore,
issueFilter: issueFilterStore,
cycleIssueKanBanView: cycleIssueKanBanViewStore,
@ -55,6 +58,14 @@ export const CycleKanBanLayout: React.FC = observer(() => {
cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
};
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
const members = projectStore?.projectMembers || null;
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = projectStore?.projectStates || null;
const estimates = null;
return (
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragEnd={onDragEnd}>
@ -67,6 +78,13 @@ export const CycleKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
) : (
<KanBanSwimLanes
@ -77,6 +95,13 @@ export const CycleKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
</DragDropContext>

View File

@ -114,10 +114,18 @@ export interface IKanBan {
display_properties: any;
kanBanToggle: any;
handleKanBanToggle: any;
states: any;
stateGroups: any;
priorities: any;
labels: any;
members: any;
projects: any;
estimates: any;
}
export const KanBan: React.FC<IKanBan> = observer(
({
export const KanBan: React.FC<IKanBan> = observer((props) => {
const {
issues,
sub_group_by,
group_by,
@ -126,107 +134,114 @@ export const KanBan: React.FC<IKanBan> = observer(
display_properties,
kanBanToggle,
handleKanBanToggle,
}) => {
const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
states,
stateGroups,
priorities,
labels,
members,
projects,
estimates,
} = props;
return (
<div className="relative w-full h-full">
{group_by && group_by === "state" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectStates}
listKey={`id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
{group_by && group_by === "state_detail.group" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={ISSUE_STATE_GROUPS}
listKey={`key`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
return (
<div className="relative w-full h-full">
{group_by && group_by === "state" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectStates}
listKey={`id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "priority" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={ISSUE_PRIORITIES}
listKey={`key`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "state_detail.group" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={ISSUE_STATE_GROUPS}
listKey={`key`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "labels" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectLabels}
listKey={`id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "priority" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={ISSUE_PRIORITIES}
listKey={`key`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "assignees" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectMembers}
listKey={`member.id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "labels" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectLabels}
listKey={`id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "created_by" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectMembers}
listKey={`member.id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
</div>
);
}
);
{group_by && group_by === "assignees" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectMembers}
listKey={`member.id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "created_by" && (
<GroupByKanBan
issues={issues}
group_by={group_by}
sub_group_by={sub_group_by}
sub_group_id={sub_group_id}
list={projectStore?.projectMembers}
listKey={`member.id`}
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
</div>
);
});

View File

@ -9,11 +9,14 @@ import { KanBan } from "./default";
// store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// constants
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export interface IModuleKanBanLayout {}
export const ModuleKanBanLayout: React.FC = observer(() => {
const {
project: projectStore,
moduleIssue: moduleIssueStore,
issueFilter: issueFilterStore,
moduleIssueKanBanView: moduleIssueKanBanViewStore,
@ -55,6 +58,14 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
moduleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
};
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
const members = projectStore?.projectMembers || null;
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = projectStore?.projectStates || null;
const estimates = null;
return (
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragEnd={onDragEnd}>
@ -67,6 +78,13 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
) : (
<KanBanSwimLanes
@ -77,6 +95,13 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
</DragDropContext>

View File

@ -1,5 +1,4 @@
import React from "react";
// react beautiful dnd
import { FC } from "react";
import { DragDropContext } from "@hello-pangea/dnd";
// mobx
import { observer } from "mobx-react-lite";
@ -9,11 +8,15 @@ import { KanBan } from "./default";
// store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// constants
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export interface IProfileIssuesKanBanLayout {}
export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
export const ProfileIssuesKanBanLayout: FC = observer(() => {
const {
workspace: workspaceStore,
project: projectStore,
profileIssues: profileIssuesStore,
profileIssueFilters: profileIssueFiltersStore,
issueKanBanView: issueKanBanViewStore,
@ -55,6 +58,14 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
issueKanBanViewStore.handleKanBanToggle(toggle, value);
};
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = workspaceStore.workspaceLabels || null;
const members = projectStore?.projectMembers || null;
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = projectStore?.workspaceProjects || null;
const estimates = null;
return (
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragEnd={onDragEnd}>
@ -67,6 +78,13 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
) : (
<KanBanSwimLanes
@ -77,6 +95,13 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
</DragDropContext>

View File

@ -76,13 +76,13 @@ export const KanBanLayout: FC = observer(() => {
display_properties={display_properties}
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
// states={states}
// stateGroups={stateGroups}
// priorities={priorities}
// labels={labels}
// members={members}
// projects={projects}
// estimates={estimates}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
) : (
<KanBanSwimLanes
@ -93,13 +93,13 @@ export const KanBanLayout: FC = observer(() => {
display_properties={display_properties}
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
// states={states}
// stateGroups={stateGroups}
// priorities={priorities}
// labels={labels}
// members={members}
// projects={projects}
// estimates={estimates}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
</DragDropContext>

View File

@ -64,9 +64,16 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
display_properties: any;
kanBanToggle: any;
handleKanBanToggle: any;
states: any;
stateGroups: any;
priorities: any;
labels: any;
members: any;
projects: any;
estimates: any;
}
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer(
({
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
const {
issues,
sub_group_by,
group_by,
@ -76,55 +83,69 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer(
display_properties,
kanBanToggle,
handleKanBanToggle,
}) => {
const calculateIssueCount = (column_id: string) => {
let issueCount = 0;
issues?.[column_id] &&
Object.keys(issues?.[column_id])?.forEach((_list: any) => {
issueCount += issues?.[column_id]?.[_list]?.length || 0;
});
return issueCount;
};
states,
stateGroups,
priorities,
labels,
members,
projects,
estimates,
} = props;
return (
<div className="relative w-full min-h-full h-max">
{list &&
list.length > 0 &&
list.map((_list: any) => (
<div className="flex-shrink-0 flex flex-col">
<div className="sticky top-[50px] w-full z-[1] bg-custom-background-90 flex items-center py-1">
<div className="flex-shrink-0 sticky left-0 bg-custom-background-90 pr-2">
<KanBanSubGroupByHeaderRoot
column_id={getValueFromObject(_list, listKey) as string}
sub_group_by={sub_group_by}
group_by={group_by}
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
</div>
<div className="w-full border-b border-custom-border-400 border-dashed" />
const calculateIssueCount = (column_id: string) => {
let issueCount = 0;
issues?.[column_id] &&
Object.keys(issues?.[column_id])?.forEach((_list: any) => {
issueCount += issues?.[column_id]?.[_list]?.length || 0;
});
return issueCount;
};
return (
<div className="relative w-full min-h-full h-max">
{list &&
list.length > 0 &&
list.map((_list: any) => (
<div className="flex-shrink-0 flex flex-col">
<div className="sticky top-[50px] w-full z-[1] bg-custom-background-90 flex items-center py-1">
<div className="flex-shrink-0 sticky left-0 bg-custom-background-90 pr-2">
<KanBanSubGroupByHeaderRoot
column_id={getValueFromObject(_list, listKey) as string}
sub_group_by={sub_group_by}
group_by={group_by}
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
</div>
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
<div className="relative">
<KanBan
issues={issues?.[getValueFromObject(_list, listKey) as string]}
sub_group_by={sub_group_by}
group_by={group_by}
sub_group_id={getValueFromObject(_list, listKey) as string}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
</div>
)}
<div className="w-full border-b border-custom-border-400 border-dashed" />
</div>
))}
</div>
);
}
);
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
<div className="relative">
<KanBan
issues={issues?.[getValueFromObject(_list, listKey) as string]}
sub_group_by={sub_group_by}
group_by={group_by}
sub_group_id={getValueFromObject(_list, listKey) as string}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
</div>
)}
</div>
))}
</div>
);
});
export interface IKanBanSwimLanes {
issues: any;
@ -134,172 +155,236 @@ export interface IKanBanSwimLanes {
display_properties: any;
kanBanToggle: any;
handleKanBanToggle: any;
states: any;
stateGroups: any;
priorities: any;
labels: any;
members: any;
projects: any;
estimates: any;
}
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
({ issues, sub_group_by, group_by, handleIssues, display_properties, kanBanToggle, handleKanBanToggle }) => {
const { project: projectStore }: RootStore = useMobxStore();
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
const {
issues,
sub_group_by,
group_by,
handleIssues,
display_properties,
kanBanToggle,
handleKanBanToggle,
states,
stateGroups,
priorities,
labels,
members,
projects,
estimates,
} = props;
return (
<div className="relative">
<div className="sticky top-0 z-[2] bg-custom-background-90 h-[50px]">
{group_by && group_by === "state" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectStates}
listKey={`id`}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
const { project: projectStore }: RootStore = useMobxStore();
{group_by && group_by === "state_detail.group" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={ISSUE_STATE_GROUPS}
listKey={`key`}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "priority" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={ISSUE_PRIORITIES}
listKey={`key`}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "labels" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectLabels}
listKey={`id`}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "assignees" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{group_by && group_by === "created_by" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
</div>
{sub_group_by && sub_group_by === "state" && (
<SubGroupSwimlane
return (
<div className="relative">
<div className="sticky top-0 z-[2] bg-custom-background-90 h-[50px]">
{group_by && group_by === "state" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectStates}
listKey={`id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{sub_group_by && sub_group_by === "state_detail.group" && (
<SubGroupSwimlane
{group_by && group_by === "state_detail.group" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={ISSUE_STATE_GROUPS}
listKey={`key`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{sub_group_by && sub_group_by === "priority" && (
<SubGroupSwimlane
{group_by && group_by === "priority" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={ISSUE_PRIORITIES}
listKey={`key`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{sub_group_by && sub_group_by === "labels" && (
<SubGroupSwimlane
{group_by && group_by === "labels" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectLabels}
listKey={`id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{sub_group_by && sub_group_by === "assignees" && (
<SubGroupSwimlane
{group_by && group_by === "assignees" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
{sub_group_by && sub_group_by === "created_by" && (
<SubGroupSwimlane
{group_by && group_by === "created_by" && (
<SubGroupSwimlaneHeader
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
)}
</div>
);
}
);
{sub_group_by && sub_group_by === "state" && (
<SubGroupSwimlane
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectStates}
listKey={`id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
{sub_group_by && sub_group_by === "state_detail.group" && (
<SubGroupSwimlane
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={ISSUE_STATE_GROUPS}
listKey={`key`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
{sub_group_by && sub_group_by === "priority" && (
<SubGroupSwimlane
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={ISSUE_PRIORITIES}
listKey={`key`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
{sub_group_by && sub_group_by === "labels" && (
<SubGroupSwimlane
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectLabels}
listKey={`id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
{sub_group_by && sub_group_by === "assignees" && (
<SubGroupSwimlane
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
{sub_group_by && sub_group_by === "created_by" && (
<SubGroupSwimlane
issues={issues}
sub_group_by={sub_group_by}
group_by={group_by}
list={projectStore?.projectMembers}
listKey={`member.id`}
handleIssues={handleIssues}
display_properties={display_properties}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
</div>
);
});

View File

@ -9,11 +9,14 @@ import { KanBan } from "./default";
// store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// constants
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export interface IViewKanBanLayout {}
export const ViewKanBanLayout: React.FC = observer(() => {
const {
project: projectStore,
issue: issueStore,
issueFilter: issueFilterStore,
issueKanBanView: issueKanBanViewStore,
@ -51,6 +54,14 @@ export const ViewKanBanLayout: React.FC = observer(() => {
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
};
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
const members = projectStore?.projectMembers || null;
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = projectStore?.projectStates || null;
const estimates = null;
return (
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragEnd={onDragEnd}>
@ -63,6 +74,13 @@ export const ViewKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={() => {}}
handleKanBanToggle={() => {}}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
) : (
<KanBanSwimLanes
@ -73,6 +91,13 @@ export const ViewKanBanLayout: React.FC = observer(() => {
display_properties={display_properties}
kanBanToggle={() => {}}
handleKanBanToggle={() => {}}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={members}
projects={projects}
estimates={estimates}
/>
)}
</DragDropContext>

View File

@ -1,6 +1,7 @@
import { FC } from "react";
// components
import { KanBanProperties } from "./properties";
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
// ui
import { Tooltip } from "@plane/ui";
@ -18,6 +19,10 @@ interface IssueBlockProps {
export const IssueBlock: FC<IssueBlockProps> = (props) => {
const { columnId, issues, handleIssues, display_properties, states, labels, members, priorities } = props;
const handleIssue = (_issue: any) => {
if (_issue && handleIssues) handleIssues(!columnId && columnId === "null" ? null : columnId, _issue);
};
return (
<>
{issues &&
@ -25,14 +30,25 @@ export const IssueBlock: FC<IssueBlockProps> = (props) => {
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 gap-3 border-b border-custom-border-200`}
className={`text-sm p-3 shadow-custom-shadow-2xs bg-custom-background-100 flex items-center gap-3 border-b border-custom-border-200 hover:bg-custom-background-80`}
>
{display_properties && display_properties?.key && (
<div className="flex-shrink-0 text-xs text-custom-text-300">ONE-{issue.sequence_id}</div>
<div className="flex-shrink-0 text-xs text-custom-text-300">
{issue?.project_detail?.identifier}-{issue.sequence_id}
</div>
)}
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div className="line-clamp-1 text-sm font-medium text-custom-text-100">{issue.name}</div>
</Tooltip>
<IssuePeekOverview
workspaceSlug={issue?.workspace_detail?.slug}
projectId={issue?.project_detail?.id}
issueId={issue?.id}
handleIssue={handleIssue}
>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div className="line-clamp-1 text-sm font-medium text-custom-text-100 w-full">{issue.name}</div>
</Tooltip>
</IssuePeekOverview>
<div className="ml-auto flex-shrink-0">
<KanBanProperties
columnId={columnId}

View File

@ -2,7 +2,6 @@ import React from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components

View File

@ -0,0 +1 @@
export * from "./root";

View File

@ -0,0 +1,45 @@
import { FC } from "react";
// packages
import { RichTextEditor } from "@plane/rich-text-editor";
// hooks
import { useDebouncedCallback } from "use-debounce";
// types
import { IIssue } from "types";
// services
import { FileService } from "services/file.service";
const fileService = new FileService();
interface IPeekOverviewIssueDetails {
workspaceSlug: string;
issue: IIssue;
issueUpdate: (issue: Partial<IIssue>) => void;
}
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) => {
const { workspaceSlug, issue, issueUpdate } = props;
const debouncedIssueDescription = useDebouncedCallback(async (_data: any) => {
issueUpdate({ ...issue, description_html: _data });
}, 1500);
return (
<div className="space-y-2">
<div className="font-medium text-sm text-custom-text-200">
{issue?.project_detail?.identifier}-{issue?.sequence_id}
</div>
<div className="font-medium text-xl">{issue?.name}</div>
<RichTextEditor
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
value={issue?.description_html}
debouncedUpdatesEnabled={false}
onChange={(description: Object, description_html: string) => {
debouncedIssueDescription(description_html);
}}
/>
</div>
);
};

View File

@ -0,0 +1,141 @@
import { FC } from "react";
// ui icons
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
import { CalendarDays, Signal } from "lucide-react";
// components
import { IssuePropertyState } from "components/issues/issue-layouts/properties/state";
import { IssuePropertyPriority } from "components/issues/issue-layouts/properties/priority";
import { IssuePropertyAssignee } from "components/issues/issue-layouts/properties/assignee";
import { IssuePropertyDate } from "components/issues/issue-layouts/properties/date";
// types
import { IIssue } from "types";
interface IPeekOverviewProperties {
issue: IIssue;
issueUpdate: (issue: Partial<IIssue>) => void;
states: any;
members: any;
priorities: any;
}
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
const { issue, issueUpdate, states, members, priorities } = props;
const handleState = (_state: string) => {
if (issueUpdate) issueUpdate({ ...issue, state: _state });
};
const handlePriority = (_priority: any) => {
if (issueUpdate) issueUpdate({ ...issue, priority: _priority });
};
const handleAssignee = (_assignees: string[]) => {
if (issueUpdate) issueUpdate({ ...issue, assignees: _assignees });
};
const handleStartDate = (_startDate: string) => {
if (issueUpdate) issueUpdate({ ...issue, start_date: _startDate });
};
const handleTargetDate = (_targetDate: string) => {
if (issueUpdate) issueUpdate({ ...issue, target_date: _targetDate });
};
return (
<div className="space-y-4">
{/* state */}
<div className="flex items-center gap-2">
<div className="flex-shrink-0 flex items-center gap-2 w-48 whitespace-nowrap">
<div className="w-4 h-4 flex justify-center items-center overflow-hidden">
<DoubleCircleIcon className="h-3.5 w-3.5 flex-shrink-0" />
</div>
<div className="font-medium text-custom-text-200 line-clamp-1">State</div>
</div>
<div className="w-full">
<IssuePropertyState
value={issue?.state || null}
dropdownArrow={false}
onChange={(id: string) => handleState(id)}
disabled={false}
list={states}
/>
</div>
</div>
{/* assignees */}
<div className="flex items-center gap-2">
<div className="flex-shrink-0 flex items-center gap-2 w-48 whitespace-nowrap">
<div className="w-4 h-4 flex justify-center items-center overflow-hidden">
<UserGroupIcon className="h-3.5 w-3.5" />
</div>
<div className="font-medium text-custom-text-200 line-clamp-1">Assignees</div>
</div>
<div className="w-full">
<IssuePropertyAssignee
value={issue?.assignees || null}
dropdownArrow={false}
onChange={(ids: string[]) => handleAssignee(ids)}
disabled={false}
list={members}
/>
</div>
</div>
{/* priority */}
<div className="flex items-center gap-2">
<div className="flex-shrink-0 flex items-center gap-2 w-48 whitespace-nowrap">
<div className="w-4 h-4 flex justify-center items-center overflow-hidden">
<Signal className="h-3.5 w-3.5" />
</div>
<div className="font-medium text-custom-text-200 line-clamp-1">Priority</div>
</div>
<div className="w-full">
<IssuePropertyPriority
value={issue?.priority || null}
dropdownArrow={false}
onChange={(id: string) => handlePriority(id)}
disabled={false}
list={priorities}
/>
</div>
</div>
{/* start_date */}
<div className="flex items-center gap-2">
<div className="flex-shrink-0 flex items-center gap-2 w-48 whitespace-nowrap">
<div className="w-4 h-4 flex justify-center items-center overflow-hidden">
<CalendarDays className="h-3.5 w-3.5" />
</div>
<div className="font-medium text-custom-text-200 line-clamp-1">Start date</div>
</div>
<div className="w-full">
<IssuePropertyDate
value={issue?.start_date || null}
onChange={(date: string) => handleStartDate(date)}
disabled={false}
placeHolder={`Start date`}
/>
</div>
</div>
{/* target_date */}
<div className="flex items-center gap-2">
<div className="flex-shrink-0 flex items-center gap-2 w-48 whitespace-nowrap">
<div className="w-4 h-4 flex justify-center items-center overflow-hidden">
<CalendarDays className="h-3.5 w-3.5" />
</div>
<div className="font-medium text-custom-text-200 line-clamp-1">Target date</div>
</div>
<div className="w-full">
<IssuePropertyDate
value={issue?.target_date || null}
onChange={(date: string) => handleTargetDate(date)}
disabled={false}
placeHolder={`Target date`}
/>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,50 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react-lite";
// components
import { IssueView } from "./view";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// types
import { IIssue } from "types";
import { RootStore } from "store/root";
// constants
import { ISSUE_PRIORITIES } from "constants/issue";
interface IIssuePeekOverview {
workspaceSlug: string;
projectId: string;
issueId: string;
handleIssue: (issue: Partial<IIssue>) => void;
children: ReactNode;
}
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const { workspaceSlug, projectId, issueId, handleIssue, children } = props;
const { project: projectStore, issueDetail: issueDetailStore }: RootStore = useMobxStore();
const states = projectStore?.projectStates || undefined;
const members = projectStore?.projectMembers || undefined;
const priorities = ISSUE_PRIORITIES || undefined;
const issueUpdate = (_data: Partial<IIssue>) => {
if (handleIssue) {
handleIssue(_data);
issueDetailStore.updateIssue(workspaceSlug, projectId, issueId, _data, undefined);
}
};
return (
<IssueView
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
states={states}
members={members}
priorities={priorities}
issueUpdate={issueUpdate}
>
{children}
</IssueView>
);
});

View File

@ -0,0 +1,206 @@
import { FC, ReactNode, useEffect, useState } from "react";
import { useRouter } from "next/router";
import { Maximize2, ArrowRight, Link, Trash, PanelRightOpen, Square, SquareCode } from "lucide-react";
import { observer } from "mobx-react-lite";
// components
import { PeekOverviewIssueDetails } from "./issue-detail";
import { PeekOverviewProperties } from "./properties";
// types
import { IIssue } from "types";
import { RootStore } from "store/root";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
interface IIssueView {
workspaceSlug: string;
projectId: string;
issueId: string;
issueUpdate: (issue: Partial<IIssue>) => void;
states: any;
members: any;
priorities: any;
children: ReactNode;
}
type TPeekModes = "side-peek" | "modal" | "full-screen";
const peekOptions: { key: TPeekModes; icon: any; title: string }[] = [
{
key: "side-peek",
icon: PanelRightOpen,
title: "Side Peek",
},
{
key: "modal",
icon: Square,
title: "Modal",
},
{
key: "full-screen",
icon: SquareCode,
title: "Full Screen",
},
];
export const IssueView: FC<IIssueView> = observer((props) => {
const { workspaceSlug, projectId, issueId, issueUpdate, states, members, priorities, children } = props;
const router = useRouter();
const { peekIssueId } = router.query as { peekIssueId: string };
const { issueDetail: issueDetailStore }: RootStore = useMobxStore();
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
const handlePeekMode = (_peek: TPeekModes) => {
if (peekMode != _peek) setPeekMode(_peek);
};
const updateRoutePeekId = () => {
if (issueId != peekIssueId) {
const { query } = router;
router.push({
pathname: router.pathname,
query: { ...query, peekIssueId: issueId },
});
}
};
const removeRoutePeekId = () => {
const { query } = router;
if (query.peekIssueId) {
delete query.peekIssueId;
router.push({
pathname: router.pathname,
query: { ...query },
});
}
};
const redirectToIssueDetail = () => {
router.push({
pathname: `/${workspaceSlug}/projects/${projectId}/issues/${issueId}`,
});
};
useEffect(() => {
if (workspaceSlug && projectId && issueId && peekIssueId && issueId === peekIssueId)
issueDetailStore.fetchIssueDetails(workspaceSlug, projectId, issueId);
}, [workspaceSlug, projectId, issueId, peekIssueId, issueDetailStore]);
const issue = issueDetailStore.getIssue;
return (
<div className="w-full !text-base">
<div onClick={updateRoutePeekId} className="w-full cursor-pointer">
{children}
</div>
{issueId === peekIssueId && (
<div
className={`fixed z-50 overflow-hidden bg-custom-background-80 flex flex-col transition-all duration-200 border border-custom-border-200 rounded shadow-custom-shadow-2xl
${peekMode === "side-peek" ? `w-full md:w-[50%] top-0 right-0 bottom-0` : ``}
${peekMode === "modal" ? `top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] w-5/6 h-5/6` : ``}
${peekMode === "full-screen" ? `top-0 right-0 bottom-0 left-0 m-4` : ``}
`}
>
{/* header */}
<div className="flex-shrink-0 w-full p-3 py-2.5 relative flex items-center gap-2 border-b border-custom-border-200">
<div
className="flex-shrink-0 overflow-hidden w-6 h-6 flex justify-center items-center rounded-sm transition-all duration-100 border border-custom-border-200 cursor-pointer hover:bg-custom-background-100"
onClick={removeRoutePeekId}
>
<ArrowRight width={12} strokeWidth={2} />
</div>
<div
className="flex-shrink-0 overflow-hidden w-6 h-6 flex justify-center items-center rounded-sm transition-all duration-100 border border-custom-border-200 cursor-pointer hover:bg-custom-background-100"
onClick={redirectToIssueDetail}
>
<Maximize2 width={12} strokeWidth={2} />
</div>
<div className="flex-shrink-0 flex items-center gap-2">
{peekOptions.map((_option) => (
<div
key={_option?.key}
className={`px-1.5 min-w-6 h-6 flex justify-center items-center gap-1 rounded-sm transition-all duration-100 border border-custom-border-200 cursor-pointer hover:bg-custom-background-100
${peekMode === _option?.key ? `bg-custom-background-100` : ``}
`}
onClick={() => handlePeekMode(_option?.key)}
>
<_option.icon width={14} strokeWidth={2} />
<div className="text-xs font-medium">{_option?.title}</div>
</div>
))}
</div>
<div className="w-full flex justify-end items-center gap-2">
<div className="px-1.5 min-w-6 h-6 text-xs font-medium flex justify-center items-center rounded-sm transition-all duration-100 border border-custom-border-200 cursor-pointer hover:bg-custom-background-100">
Subscribe
</div>
<div className="overflow-hidden w-6 h-6 flex justify-center items-center rounded-sm transition-all duration-100 border border-custom-border-200 cursor-pointer hover:bg-custom-background-100">
<Link width={12} strokeWidth={2} />
</div>
<div className="overflow-hidden w-6 h-6 flex justify-center items-center rounded-sm transition-all duration-100 border border-custom-border-200 cursor-pointer hover:bg-custom-background-100">
<Trash width={12} strokeWidth={2} />
</div>
</div>
</div>
{/* content */}
<div className="w-full h-full overflow-hidden overflow-y-auto">
{issueDetailStore?.loader && !issue ? (
<div className="text-center py-10">Loading...</div>
) : (
issue && (
<>
{["side-peek", "modal"].includes(peekMode) ? (
<div className="space-y-8 p-3 py-5">
<PeekOverviewIssueDetails workspaceSlug={workspaceSlug} issue={issue} issueUpdate={issueUpdate} />
{/* reactions */}
<PeekOverviewProperties
issue={issue}
issueUpdate={issueUpdate}
states={states}
members={members}
priorities={priorities}
/>
{/* activity */}
</div>
) : (
<div className="w-full h-full flex">
<div className="w-full h-full space-y-8 p-3 py-5">
<PeekOverviewIssueDetails
workspaceSlug={workspaceSlug}
issue={issue}
issueUpdate={issueUpdate}
/>
{/* reactions */}
{/* activity */}
</div>
<div className="flex-shrink-0 !w-[400px] h-full border-l border-custom-border-200 p-3 py-5">
<PeekOverviewProperties
issue={issue}
issueUpdate={issueUpdate}
states={states}
members={members}
priorities={priorities}
/>
</div>
</div>
)}
</>
)
)}
</div>
</div>
)}
</div>
);
});

View File

@ -1,4 +1,4 @@
import { observable, action, makeObservable, runInAction } from "mobx";
import { observable, action, makeObservable, runInAction, computed } from "mobx";
// services
import { IssueService } from "services/issue";
// types
@ -20,12 +20,22 @@ export interface IIssueDetailStore {
setPeekId: (issueId: string | null) => void;
setPeekMode: (issueId: IPeekMode | null) => void;
// computed
getIssue: IIssue | null;
// fetch issue details
fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => void;
// creating issue
createIssue: (workspaceSlug: string, projectId: string, data: Partial<IIssue>, user: IUser) => void;
// updating issue
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<IIssue>, user: IUser) => void;
updateIssue: (
workspaceId: string,
projectId: string,
issueId: string,
data: Partial<IIssue>,
user: IUser | undefined
) => void;
// deleting issue
deleteIssue: (workspaceSlug: string, projectId: string, issueId: string, user: IUser) => void;
}
@ -57,6 +67,8 @@ export class IssueDetailStore implements IIssueDetailStore {
issues: observable.ref,
getIssue: computed,
setPeekId: action,
setPeekMode: action,
@ -70,6 +82,12 @@ export class IssueDetailStore implements IIssueDetailStore {
this.issueService = new IssueService();
}
get getIssue() {
if (!this.peekId) return null;
const _issue = this.issues[this.peekId];
return _issue || null;
}
setPeekId = (issueId: string | null) => (this.peekId = issueId);
setPeekMode = (mode: IPeekMode | null) => (this.peekMode = mode);
@ -78,6 +96,7 @@ export class IssueDetailStore implements IIssueDetailStore {
try {
this.loader = true;
this.error = null;
this.peekId = issueId;
const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId);