feat: manual ordering of issues (#305)

This commit is contained in:
Aaryan Khandelwal 2023-02-20 19:19:46 +05:30 committed by GitHub
parent 202096500e
commit 818fe3ecf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 85 additions and 32 deletions

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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,
]

View File

@ -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.",
});
});
};

View File

@ -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;

View File

@ -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: {

View File

@ -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"),
])
);
}

View File

@ -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;