mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
feat: manual ordering of issues (#305)
This commit is contained in:
parent
202096500e
commit
818fe3ecf7
@ -29,7 +29,7 @@ type Props = {
|
||||
addIssueToState: () => void;
|
||||
handleDeleteIssue: (issue: IIssue) => void;
|
||||
openIssuesListModal?: (() => void) | null;
|
||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | null;
|
||||
handleTrashBox: (isDragging: boolean) => void;
|
||||
removeIssue: ((bridgeId: string) => void) | null;
|
||||
userAuth: UserAuth;
|
||||
@ -92,6 +92,22 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{orderBy !== "sort_order" && (
|
||||
<>
|
||||
<div
|
||||
className={`absolute ${
|
||||
snapshot.isDraggingOver ? "block" : "hidden"
|
||||
} top-0 left-0 h-full w-full bg-indigo-200 opacity-50 pointer-events-none z-[99999998]`}
|
||||
/>
|
||||
<div
|
||||
className={`absolute ${
|
||||
snapshot.isDraggingOver ? "block" : "hidden"
|
||||
} top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 text-xs whitespace-nowrap bg-white p-2 rounded pointer-events-none z-[99999999]`}
|
||||
>
|
||||
This board is order by {orderBy}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{groupedByIssues[groupTitle].map((issue, index: number) => (
|
||||
<Draggable
|
||||
key={issue.id}
|
||||
@ -124,7 +140,7 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
))}
|
||||
<span
|
||||
style={{
|
||||
display: orderBy === "manual" ? "inline" : "none",
|
||||
display: orderBy === "sort_order" ? "inline" : "none",
|
||||
}}
|
||||
>
|
||||
{provided.placeholder}
|
||||
|
@ -49,7 +49,7 @@ type Props = {
|
||||
editIssue: () => void;
|
||||
removeIssue?: (() => void) | null;
|
||||
handleDeleteIssue: (issue: IIssue) => void;
|
||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | null;
|
||||
handleTrashBox: (isDragging: boolean) => void;
|
||||
userAuth: UserAuth;
|
||||
};
|
||||
@ -150,7 +150,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
style: DraggingStyle | NotDraggingStyle | undefined,
|
||||
snapshot: DraggableStateSnapshot
|
||||
) {
|
||||
if (orderBy === "manual") return style;
|
||||
if (orderBy === "sort_order") return style;
|
||||
if (!snapshot.isDragging) return {};
|
||||
if (!snapshot.isDropAnimating) {
|
||||
return style;
|
||||
@ -179,12 +179,13 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
});
|
||||
});
|
||||
};
|
||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
||||
|
||||
useEffect(() => {
|
||||
if (snapshot.isDragging) handleTrashBox(snapshot.isDragging);
|
||||
}, [snapshot, handleTrashBox]);
|
||||
|
||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`rounded border bg-white shadow-sm ${
|
||||
@ -198,13 +199,6 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
<div className="group/card relative select-none p-2">
|
||||
{!isNotAllowed && (
|
||||
<div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover/card:opacity-100">
|
||||
{/* <button
|
||||
type="button"
|
||||
className="grid h-7 w-7 place-items-center rounded bg-white p-1 text-red-500 outline-none duration-300 hover:bg-red-50"
|
||||
onClick={() => handleDeleteIssue(issue)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button> */}
|
||||
{type && !isNotAllowed && (
|
||||
<CustomMenu width="auto" ellipsis>
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>Copy issue link</CustomMenu.MenuItem>
|
||||
|
@ -130,7 +130,10 @@ export const IssuesFilterView: React.FC<Props> = ({ issues }) => {
|
||||
option.key === "priority" ? null : (
|
||||
<CustomMenu.MenuItem
|
||||
key={option.key}
|
||||
onClick={() => setOrderBy(option.key)}
|
||||
onClick={() => {
|
||||
console.log(option.key);
|
||||
setOrderBy(option.key);
|
||||
}}
|
||||
>
|
||||
{option.name}
|
||||
</CustomMenu.MenuItem>
|
||||
|
@ -67,7 +67,12 @@ export const IssuesView: React.FC<Props> = ({
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const { issueView, groupedByIssues, groupByProperty: selectedGroup } = useIssueView(issues);
|
||||
const {
|
||||
issueView,
|
||||
groupedByIssues,
|
||||
groupByProperty: selectedGroup,
|
||||
orderBy,
|
||||
} = useIssueView(issues);
|
||||
|
||||
const { data: stateGroups } = useSWR(
|
||||
workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
|
||||
@ -101,10 +106,25 @@ export const IssuesView: React.FC<Props> = ({
|
||||
const { source, destination } = result;
|
||||
|
||||
const draggedItem = groupedByIssues[source.droppableId][source.index];
|
||||
let newSortOrder = draggedItem.sort_order;
|
||||
|
||||
if (destination.droppableId === "trashBox") {
|
||||
handleDeleteIssue(draggedItem);
|
||||
} else {
|
||||
if (orderBy === "sort_order") {
|
||||
const destinationGroupArray = groupedByIssues[destination.droppableId];
|
||||
|
||||
if (destination.index === 0) newSortOrder = destinationGroupArray[0].sort_order - 10000;
|
||||
else if (destination.index === destinationGroupArray.length)
|
||||
newSortOrder =
|
||||
destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000;
|
||||
else
|
||||
newSortOrder =
|
||||
(destinationGroupArray[destination.index - 1].sort_order +
|
||||
destinationGroupArray[destination.index].sort_order) /
|
||||
2;
|
||||
}
|
||||
|
||||
if (source.droppableId !== destination.droppableId) {
|
||||
const sourceGroup = source.droppableId; // source group id
|
||||
const destinationGroup = destination.droppableId; // destination group id
|
||||
@ -127,6 +147,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
issue_detail: {
|
||||
...draggedItem,
|
||||
priority: destinationGroup,
|
||||
sort_order: newSortOrder,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -149,6 +170,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
issue_detail: {
|
||||
...draggedItem,
|
||||
priority: destinationGroup,
|
||||
sort_order: newSortOrder,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -169,6 +191,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
return {
|
||||
...draggedItem,
|
||||
priority: destinationGroup,
|
||||
sort_order: newSortOrder,
|
||||
};
|
||||
|
||||
return issue;
|
||||
@ -183,6 +206,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
issuesService
|
||||
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
|
||||
priority: destinationGroup,
|
||||
sort_order: newSortOrder,
|
||||
})
|
||||
.then((res) => {
|
||||
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
||||
@ -212,6 +236,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
...draggedItem,
|
||||
state_detail: destinationState,
|
||||
state: destinationStateId,
|
||||
sort_order: newSortOrder,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -235,6 +260,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
...draggedItem,
|
||||
state_detail: destinationState,
|
||||
state: destinationStateId,
|
||||
sort_order: newSortOrder,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -256,6 +282,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
...draggedItem,
|
||||
state_detail: destinationState,
|
||||
state: destinationStateId,
|
||||
sort_order: newSortOrder,
|
||||
};
|
||||
|
||||
return issue;
|
||||
@ -270,6 +297,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
issuesService
|
||||
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
|
||||
state: destinationStateId,
|
||||
sort_order: newSortOrder,
|
||||
})
|
||||
.then((res) => {
|
||||
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
||||
@ -288,6 +316,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
groupedByIssues,
|
||||
projectId,
|
||||
selectedGroup,
|
||||
orderBy,
|
||||
states,
|
||||
handleDeleteIssue,
|
||||
]
|
||||
|
@ -39,12 +39,18 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
||||
.then((res) => {
|
||||
mutate(CYCLE_LIST(projectId as string));
|
||||
handleClose();
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle created successfully.",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error",
|
||||
message: "Error in creating cycle. Please try again!",
|
||||
title: "Error!",
|
||||
message: "Error in creating cycle. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -55,12 +61,18 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
|
||||
.then((res) => {
|
||||
mutate(CYCLE_LIST(projectId as string));
|
||||
handleClose();
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error",
|
||||
message: "Error in updating cycle. Please try again!",
|
||||
title: "Error!",
|
||||
message: "Error in updating cycle. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -9,14 +9,12 @@ export const GROUP_BY_OPTIONS: Array<{ name: string; key: NestedKeyOf<IIssue> |
|
||||
{ name: "None", key: null },
|
||||
];
|
||||
|
||||
export const ORDER_BY_OPTIONS: Array<{ name: string; key: NestedKeyOf<IIssue> | "manual" | null }> =
|
||||
[
|
||||
// { name: "Manual", key: "manual" },
|
||||
{ name: "Last created", key: "created_at" },
|
||||
{ name: "Last updated", key: "updated_at" },
|
||||
{ name: "Priority", key: "priority" },
|
||||
// { name: "None", key: null },
|
||||
];
|
||||
export const ORDER_BY_OPTIONS: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
||||
{ name: "Manual", key: "sort_order" },
|
||||
{ name: "Last created", key: "created_at" },
|
||||
{ name: "Last updated", key: "updated_at" },
|
||||
{ name: "Priority", key: "priority" },
|
||||
];
|
||||
|
||||
export const FILTER_ISSUE_OPTIONS: Array<{
|
||||
name: string;
|
||||
|
@ -19,7 +19,7 @@ type IssueViewProps = {
|
||||
issueView: "list" | "kanban" | null;
|
||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||
filterIssue: "activeIssue" | "backlogIssue" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | null;
|
||||
};
|
||||
|
||||
type ReducerActionType = {
|
||||
@ -34,12 +34,12 @@ type ReducerActionType = {
|
||||
};
|
||||
|
||||
type ContextType = {
|
||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | null;
|
||||
issueView: "list" | "kanban" | null;
|
||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||
filterIssue: "activeIssue" | "backlogIssue" | null;
|
||||
setGroupByProperty: (property: NestedKeyOf<IIssue> | null) => void;
|
||||
setOrderBy: (property: NestedKeyOf<IIssue> | "manual" | null) => void;
|
||||
setOrderBy: (property: NestedKeyOf<IIssue> | null) => void;
|
||||
setFilterIssue: (property: "activeIssue" | "backlogIssue" | null) => void;
|
||||
resetFilterToDefault: () => void;
|
||||
setNewFilterDefaultView: () => void;
|
||||
@ -51,7 +51,7 @@ type StateType = {
|
||||
issueView: "list" | "kanban" | null;
|
||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||
filterIssue: "activeIssue" | "backlogIssue" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||
orderBy: NestedKeyOf<IIssue> | null;
|
||||
};
|
||||
type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType;
|
||||
|
||||
@ -220,7 +220,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
||||
);
|
||||
|
||||
const setOrderBy = useCallback(
|
||||
(property: NestedKeyOf<IIssue> | "manual" | null) => {
|
||||
(property: NestedKeyOf<IIssue> | null) => {
|
||||
dispatch({
|
||||
type: "SET_ORDER_BY_PROPERTY",
|
||||
payload: {
|
||||
|
@ -97,7 +97,7 @@ const useIssueView = (projectIssues: IIssue[]) => {
|
||||
groupedByIssues = Object.fromEntries(
|
||||
Object.entries(groupedByIssues).map(([key, value]) => [
|
||||
key,
|
||||
orderArrayBy(value, orderBy, "descending"),
|
||||
orderArrayBy(value, orderBy, orderBy === "sort_order" ? "ascending" : "descending"),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
1
apps/app/types/issues.d.ts
vendored
1
apps/app/types/issues.d.ts
vendored
@ -96,6 +96,7 @@ export interface IIssue {
|
||||
project: string;
|
||||
project_detail: IProject;
|
||||
sequence_id: number;
|
||||
sort_order: number;
|
||||
sprints: string | null;
|
||||
start_date: string | null;
|
||||
state: string;
|
||||
|
Loading…
Reference in New Issue
Block a user