forked from github/plane
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:
parent
704fe155af
commit
9b96e297b3
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
1
web/components/issues/issue-peek-overview/index.ts
Normal file
1
web/components/issues/issue-peek-overview/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./root";
|
45
web/components/issues/issue-peek-overview/issue-detail.tsx
Normal file
45
web/components/issues/issue-peek-overview/issue-detail.tsx
Normal 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>
|
||||
);
|
||||
};
|
141
web/components/issues/issue-peek-overview/properties.tsx
Normal file
141
web/components/issues/issue-peek-overview/properties.tsx
Normal 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>
|
||||
);
|
||||
};
|
50
web/components/issues/issue-peek-overview/root.tsx
Normal file
50
web/components/issues/issue-peek-overview/root.tsx
Normal 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>
|
||||
);
|
||||
});
|
206
web/components/issues/issue-peek-overview/view.tsx
Normal file
206
web/components/issues/issue-peek-overview/view.tsx
Normal 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>
|
||||
);
|
||||
});
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user