fix: Kanban related issues (#3436)

* fix for drag and drop issues

* add horizontal scroll for kanban

* fix all issues quick action overlap

---------

Co-authored-by: Rahul R <rahul.ramesha@plane.so>
This commit is contained in:
rahulramesha 2024-01-23 16:09:37 +05:30 committed by GitHub
parent c6b756d918
commit e36b7a5ab9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 80 additions and 81 deletions

View File

@ -236,51 +236,53 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
</div> </div>
)} )}
<div className="relative h-full w-max min-w-full bg-custom-background-90 px-2"> <div className="horizontal-scroll-enable relative h-full w-full overflow-auto bg-custom-background-90">
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}> <div className="relative h-full w-max min-w-full bg-custom-background-90 px-2">
{/* drag and delete component */} <DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
<div {/* drag and delete component */}
className={`fixed left-1/2 -translate-x-1/2 ${ <div
isDragStarted ? "z-40" : "" className={`fixed left-1/2 -translate-x-1/2 ${
} top-3 mx-3 flex w-72 items-center justify-center`} isDragStarted ? "z-40" : ""
> } top-3 mx-3 flex w-72 items-center justify-center`}
<Droppable droppableId="issue-trash-box" isDropDisabled={!isDragStarted}> >
{(provided, snapshot) => ( <Droppable droppableId="issue-trash-box" isDropDisabled={!isDragStarted}>
<div {(provided, snapshot) => (
className={`${ <div
isDragStarted ? `opacity-100` : `opacity-0` className={`${
} flex w-full items-center justify-center rounded border-2 border-red-500/20 bg-custom-background-100 px-3 py-5 text-xs font-medium italic text-red-500 ${ isDragStarted ? `opacity-100` : `opacity-0`
snapshot.isDraggingOver ? "bg-red-500 opacity-70 blur-2xl" : "" } flex w-full items-center justify-center rounded border-2 border-red-500/20 bg-custom-background-100 px-3 py-5 text-xs font-medium italic text-red-500 ${
} transition duration-300`} snapshot.isDraggingOver ? "bg-red-500 opacity-70 blur-2xl" : ""
ref={provided.innerRef} } transition duration-300`}
{...provided.droppableProps} ref={provided.innerRef}
> {...provided.droppableProps}
Drop here to delete the issue. >
</div> Drop here to delete the issue.
)} </div>
</Droppable> )}
</div> </Droppable>
</div>
<KanBanView <KanBanView
issuesMap={issueMap} issuesMap={issueMap}
issueIds={issueIds} issueIds={issueIds}
displayProperties={displayProperties} displayProperties={displayProperties}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={renderQuickActions} quickActions={renderQuickActions}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
enableQuickIssueCreate={enableQuickAdd} enableQuickIssueCreate={enableQuickAdd}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true} showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
quickAddCallback={issues?.quickAddIssue} quickAddCallback={issues?.quickAddIssue}
viewId={viewId} viewId={viewId}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed} disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
storeType={storeType} storeType={storeType}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}
/> />
</DragDropContext> </DragDropContext>
</div>
</div> </div>
</> </>
); );

View File

@ -4,7 +4,7 @@ import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
// components // components
import { KanbanIssueBlock } from "components/issues"; import { KanbanIssueBlock } from "components/issues";
import { Draggable } from "@hello-pangea/dnd"; import { Draggable, DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd";
interface IssueBlocksListProps { interface IssueBlocksListProps {
sub_group_id: string; sub_group_id: string;
@ -43,8 +43,8 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`; if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
return ( return (
<Draggable key={draggableId} draggableId={draggableId} index={index}> <Draggable key={draggableId} draggableId={draggableId} index={index} isDragDisabled={isDragDisabled}>
{(provided, snapshot) => ( {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
<KanbanIssueBlock <KanbanIssueBlock
key={`kanban-issue-block-${issueId}`} key={`kanban-issue-block-${issueId}`}
issueId={issueId} issueId={issueId}

View File

@ -199,7 +199,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
group_by={group_by} group_by={group_by}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
isDragDisabled={!issueKanBanView?.canUserDragDrop} isDragDisabled={!issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by)}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}

View File

@ -100,6 +100,19 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
[updateFilters, workspaceSlug] [updateFilters, workspaceSlug]
); );
const renderQuickActions = useCallback(
(issue: TIssue, customActionButton?: React.ReactElement, portalElement?: HTMLDivElement | null) => (
<AllIssueQuickActions
customActionButton={customActionButton}
issue={issue}
handleUpdate={async () => handleIssues({ ...issue }, EIssueActions.UPDATE)}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
portalElement={portalElement}
/>
),
[handleIssues]
);
return ( return (
<div className="relative flex h-full w-full flex-col overflow-hidden"> <div className="relative flex h-full w-full flex-col overflow-hidden">
{!globalViewId || globalViewId !== dataViewId || loader === "init-loader" || !issueIds ? ( {!globalViewId || globalViewId !== dataViewId || loader === "init-loader" || !issueIds ? (
@ -119,13 +132,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
displayFilters={issueFilters?.displayFilters ?? {}} displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate} handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issueIds={issueIds} issueIds={issueIds}
quickActions={(issue) => ( quickActions={renderQuickActions}
<AllIssueQuickActions
issue={issue}
handleUpdate={async () => handleIssues({ ...issue }, EIssueActions.UPDATE)}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
/>
)}
handleIssues={handleIssues} handleIssues={handleIssues}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
viewId={globalViewId} viewId={globalViewId}

View File

@ -8,7 +8,7 @@ export const orderStateGroups = (unorderedStateGroups: IStateResponse | undefine
}; };
export const sortStates = (states: IState[]) => { export const sortStates = (states: IState[]) => {
if (!states || states.length === 0) return null; if (!states || states.length === 0) return;
return states.sort((stateA, stateB) => { return states.sort((stateA, stateB) => {
if (stateA.group === stateB.group) { if (stateA.group === stateB.group) {

View File

@ -167,7 +167,7 @@ export class IssueHelperStore implements TIssueHelperStore {
array = reverse(sortBy(array, "created_at")); array = reverse(sortBy(array, "created_at"));
switch (key) { switch (key) {
case "sort_order": case "sort_order":
return reverse(sortBy(array, "sort_order")); return sortBy(array, "sort_order");
case "state__name": case "state__name":
return reverse(sortBy(array, "state")); return reverse(sortBy(array, "state"));

View File

@ -1,4 +1,5 @@
import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { action, computed, makeObservable, observable } from "mobx";
import { computedFn } from "mobx-utils";
import { IssueRootStore } from "./root.store"; import { IssueRootStore } from "./root.store";
// types // types
@ -8,7 +9,7 @@ export interface IIssueKanBanViewStore {
subgroupByIssuesVisibility: string[]; subgroupByIssuesVisibility: string[];
}; };
// computed // computed
canUserDragDrop: boolean; getCanUserDragDrop: (order_by: string | null, group_by: string | null, sub_group_by?: string | null) => boolean;
canUserDragDropVertically: boolean; canUserDragDropVertically: boolean;
canUserDragDropHorizontally: boolean; canUserDragDropHorizontally: boolean;
// actions // actions
@ -27,7 +28,6 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore {
makeObservable(this, { makeObservable(this, {
kanBanToggle: observable, kanBanToggle: observable,
// computed // computed
canUserDragDrop: computed,
canUserDragDropVertically: computed, canUserDragDropVertically: computed,
canUserDragDropHorizontally: computed, canUserDragDropHorizontally: computed,
@ -38,25 +38,13 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore {
this.rootStore = _rootStore; this.rootStore = _rootStore;
} }
get canUserDragDrop() { getCanUserDragDrop = computedFn((group_by: string | null, sub_group_by?: string | null) => {
return true; if (group_by && ["state", "priority"].includes(group_by)) {
if (this.rootStore.issueDetail.peekIssue?.issueId) return false; if (!sub_group_by) return true;
// FIXME: uncomment and fix if (sub_group_by && ["state", "priority"].includes(sub_group_by)) return true;
// if ( }
// this.rootStore?.issueFilter?.userDisplayFilters?.order_by && return false;
// this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" && });
// this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
// ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by)
// ) {
// if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true;
// if (
// this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by &&
// ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by)
// )
// return true;
// }
// return false;
}
get canUserDragDropVertically() { get canUserDragDropVertically() {
return false; return false;

View File

@ -8,6 +8,8 @@ import { RootStore } from "./root.store";
import { IState } from "@plane/types"; import { IState } from "@plane/types";
// services // services
import { ProjectStateService } from "services/project"; import { ProjectStateService } from "services/project";
// helpers
import { sortStates } from "helpers/state.helper";
export interface IStateStore { export interface IStateStore {
//Loaders //Loaders
@ -78,7 +80,7 @@ export class StateStore implements IStateStore {
const projectId = this.router.projectId; const projectId = this.router.projectId;
const worksapceSlug = this.router.workspaceSlug || ""; const worksapceSlug = this.router.workspaceSlug || "";
if (!projectId || !(this.fetchedMap[projectId] || this.fetchedMap[worksapceSlug])) return; if (!projectId || !(this.fetchedMap[projectId] || this.fetchedMap[worksapceSlug])) return;
return Object.values(this.stateMap).filter((state) => state.project_id === projectId); return sortStates(Object.values(this.stateMap).filter((state) => state.project_id === projectId));
} }
/** /**
@ -106,7 +108,7 @@ export class StateStore implements IStateStore {
getProjectStates = computedFn((projectId: string) => { getProjectStates = computedFn((projectId: string) => {
const worksapceSlug = this.router.workspaceSlug || ""; const worksapceSlug = this.router.workspaceSlug || "";
if (!projectId || !(this.fetchedMap[projectId] || this.fetchedMap[worksapceSlug])) return; if (!projectId || !(this.fetchedMap[projectId] || this.fetchedMap[worksapceSlug])) return;
return Object.values(this.stateMap).filter((state) => state.project_id === projectId); return sortStates(Object.values(this.stateMap).filter((state) => state.project_id === projectId));
}); });
/** /**