From 818fe3ecf7a2e346f1e98e2244bc9338107d01d1 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 20 Feb 2023 19:19:46 +0530 Subject: [PATCH] feat: manual ordering of issues (#305) --- .../core/board-view/single-board.tsx | 20 ++++++++++-- .../core/board-view/single-issue.tsx | 14 +++------ .../components/core/issues-view-filter.tsx | 5 ++- apps/app/components/core/issues-view.tsx | 31 ++++++++++++++++++- apps/app/components/cycles/modal.tsx | 20 +++++++++--- apps/app/constants/issue.ts | 14 ++++----- apps/app/contexts/issue-view.context.tsx | 10 +++--- apps/app/hooks/use-issue-view.tsx | 2 +- apps/app/types/issues.d.ts | 1 + 9 files changed, 85 insertions(+), 32 deletions(-) diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx index b9600a47c..bf8694890 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -29,7 +29,7 @@ type Props = { addIssueToState: () => void; handleDeleteIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; - orderBy: NestedKeyOf | "manual" | null; + orderBy: NestedKeyOf | null; handleTrashBox: (isDragging: boolean) => void; removeIssue: ((bridgeId: string) => void) | null; userAuth: UserAuth; @@ -92,6 +92,22 @@ export const SingleBoard: React.FC = ({ ref={provided.innerRef} {...provided.droppableProps} > + {orderBy !== "sort_order" && ( + <> +
+
+ This board is order by {orderBy} +
+ + )} {groupedByIssues[groupTitle].map((issue, index: number) => ( = ({ ))} {provided.placeholder} diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 010e8ff8a..7f6f7d025 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -49,7 +49,7 @@ type Props = { editIssue: () => void; removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; - orderBy: NestedKeyOf | "manual" | null; + orderBy: NestedKeyOf | null; handleTrashBox: (isDragging: boolean) => void; userAuth: UserAuth; }; @@ -150,7 +150,7 @@ export const SingleBoardIssue: React.FC = ({ 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 = ({ }); }); }; - const isNotAllowed = userAuth.isGuest || userAuth.isViewer; useEffect(() => { if (snapshot.isDragging) handleTrashBox(snapshot.isDragging); }, [snapshot, handleTrashBox]); + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + return (
= ({
{!isNotAllowed && (
- {/* */} {type && !isNotAllowed && ( Copy issue link diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 3ee207727..4c4a5184a 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -130,7 +130,10 @@ export const IssuesFilterView: React.FC = ({ issues }) => { option.key === "priority" ? null : ( setOrderBy(option.key)} + onClick={() => { + console.log(option.key); + setOrderBy(option.key); + }} > {option.name} diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 8215ce4b5..90c9e582e 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -67,7 +67,12 @@ export const IssuesView: React.FC = ({ 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 = ({ 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 = ({ issue_detail: { ...draggedItem, priority: destinationGroup, + sort_order: newSortOrder, }, }; } @@ -149,6 +170,7 @@ export const IssuesView: React.FC = ({ issue_detail: { ...draggedItem, priority: destinationGroup, + sort_order: newSortOrder, }, }; } @@ -169,6 +191,7 @@ export const IssuesView: React.FC = ({ return { ...draggedItem, priority: destinationGroup, + sort_order: newSortOrder, }; return issue; @@ -183,6 +206,7 @@ export const IssuesView: React.FC = ({ 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 = ({ ...draggedItem, state_detail: destinationState, state: destinationStateId, + sort_order: newSortOrder, }, }; } @@ -235,6 +260,7 @@ export const IssuesView: React.FC = ({ ...draggedItem, state_detail: destinationState, state: destinationStateId, + sort_order: newSortOrder, }, }; } @@ -256,6 +282,7 @@ export const IssuesView: React.FC = ({ ...draggedItem, state_detail: destinationState, state: destinationStateId, + sort_order: newSortOrder, }; return issue; @@ -270,6 +297,7 @@ export const IssuesView: React.FC = ({ 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 = ({ groupedByIssues, projectId, selectedGroup, + orderBy, states, handleDeleteIssue, ] diff --git a/apps/app/components/cycles/modal.tsx b/apps/app/components/cycles/modal.tsx index 43c2a1b7b..3b537bcad 100644 --- a/apps/app/components/cycles/modal.tsx +++ b/apps/app/components/cycles/modal.tsx @@ -39,12 +39,18 @@ export const CreateUpdateCycleModal: React.FC = ({ .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 = ({ .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.", }); }); }; diff --git a/apps/app/constants/issue.ts b/apps/app/constants/issue.ts index e37a8079d..46657d762 100644 --- a/apps/app/constants/issue.ts +++ b/apps/app/constants/issue.ts @@ -9,14 +9,12 @@ export const GROUP_BY_OPTIONS: Array<{ name: string; key: NestedKeyOf | { name: "None", key: null }, ]; -export const ORDER_BY_OPTIONS: Array<{ name: string; key: NestedKeyOf | "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 | 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; diff --git a/apps/app/contexts/issue-view.context.tsx b/apps/app/contexts/issue-view.context.tsx index 9a2e31a81..adbb688f3 100644 --- a/apps/app/contexts/issue-view.context.tsx +++ b/apps/app/contexts/issue-view.context.tsx @@ -19,7 +19,7 @@ type IssueViewProps = { issueView: "list" | "kanban" | null; groupByProperty: NestedKeyOf | null; filterIssue: "activeIssue" | "backlogIssue" | null; - orderBy: NestedKeyOf | "manual" | null; + orderBy: NestedKeyOf | null; }; type ReducerActionType = { @@ -34,12 +34,12 @@ type ReducerActionType = { }; type ContextType = { - orderBy: NestedKeyOf | "manual" | null; + orderBy: NestedKeyOf | null; issueView: "list" | "kanban" | null; groupByProperty: NestedKeyOf | null; filterIssue: "activeIssue" | "backlogIssue" | null; setGroupByProperty: (property: NestedKeyOf | null) => void; - setOrderBy: (property: NestedKeyOf | "manual" | null) => void; + setOrderBy: (property: NestedKeyOf | null) => void; setFilterIssue: (property: "activeIssue" | "backlogIssue" | null) => void; resetFilterToDefault: () => void; setNewFilterDefaultView: () => void; @@ -51,7 +51,7 @@ type StateType = { issueView: "list" | "kanban" | null; groupByProperty: NestedKeyOf | null; filterIssue: "activeIssue" | "backlogIssue" | null; - orderBy: NestedKeyOf | "manual" | null; + orderBy: NestedKeyOf | 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 | "manual" | null) => { + (property: NestedKeyOf | null) => { dispatch({ type: "SET_ORDER_BY_PROPERTY", payload: { diff --git a/apps/app/hooks/use-issue-view.tsx b/apps/app/hooks/use-issue-view.tsx index 4256869fb..43d7838cf 100644 --- a/apps/app/hooks/use-issue-view.tsx +++ b/apps/app/hooks/use-issue-view.tsx @@ -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"), ]) ); } diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index 40153945d..14cc79bbc 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -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;